1 #include "part_wizard.hpp"
2 #include "pool/package.hpp"
3 #include "util/util.hpp"
4 #include "util/geom_util.hpp"
5 #include "pad_editor.hpp"
6 #include "gate_editor.hpp"
7 #include "widgets/location_entry.hpp"
8 #include "util/str_util.hpp"
9 #include "util/gtk_util.hpp"
10 #include "util/pool_completion.hpp"
11 #include "nlohmann/json.hpp"
12 #include "pool-prj-mgr/pool-prj-mgr-app_win.hpp"
13 #include "util/csv.hpp"
14 #include "widgets/tag_entry.hpp"
15 #include "widgets/pool_browser_package.hpp"
16 #include "widgets/preview_canvas.hpp"
17 #include "canvas/canvas_gl.hpp"
18 
19 namespace horizon {
pack_location_entry(const Glib::RefPtr<Gtk::Builder> & x,const std::string & w,Gtk::Button ** button_other)20 LocationEntry *PartWizard::pack_location_entry(const Glib::RefPtr<Gtk::Builder> &x, const std::string &w,
21                                                Gtk::Button **button_other)
22 {
23     auto en = Gtk::manage(new LocationEntry(pool_base_path));
24     en->set_append_json(true);
25     if (button_other) {
26         *button_other = Gtk::manage(new Gtk::Button());
27         en->pack_start(**button_other, false, false);
28         (*button_other)->show();
29     }
30     Gtk::Box *box;
31     x->get_widget(w, box);
32     box->pack_start(*en, true, true, 0);
33     en->show();
34     return en;
35 }
36 
PartWizard(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,const UUID & pkg_uuid,const std::string & bp,class Pool & po,PoolProjectManagerAppWindow & aw)37 PartWizard::PartWizard(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x, const UUID &pkg_uuid,
38                        const std::string &bp, class Pool &po, PoolProjectManagerAppWindow &aw)
39     : Gtk::Window(cobject), pool_base_path(bp), pool(po), part(UUID::random()), entity(UUID::random()), appwin(aw),
40       state_store(this, "part-wizard")
41 {
42     x->get_widget("header", header);
43     x->get_widget("stack", stack);
44     x->get_widget("button_back", button_back);
45     x->get_widget("button_next", button_next);
46     x->get_widget("button_finish", button_finish);
47     x->get_widget("button_select", button_select);
48 
49     x->get_widget("pads_lb", pads_lb);
50     x->get_widget("link_pads", button_link_pads);
51     x->get_widget("unlink_pads", button_unlink_pads);
52     x->get_widget("import_pads", button_import_pads);
53     x->get_widget("page_assign", page_assign);
54     x->get_widget("page_edit", page_edit);
55     x->get_widget("edit_left_box", edit_left_box);
56 
57     x->get_widget("entity_name", entity_name_entry);
58     x->get_widget("entity_name_from_mpn", entity_name_from_mpn_button);
59     x->get_widget("entity_prefix", entity_prefix_entry);
60     {
61         Gtk::Box *tag_box;
62         x->get_widget("entity_tags", tag_box);
63         entity_tags_entry = Gtk::manage(new TagEntry(pool, ObjectType::ENTITY, true));
64         entity_tags_entry->show();
65         tag_box->pack_start(*entity_tags_entry, true, true, 0);
66     }
67 
68     x->get_widget("part_mpn", part_mpn_entry);
69     x->get_widget("part_value", part_value_entry);
70     x->get_widget("part_manufacturer", part_manufacturer_entry);
71     x->get_widget("part_description", part_description_entry);
72     x->get_widget("part_datasheet", part_datasheet_entry);
73     {
74         Gtk::Box *tag_box;
75         x->get_widget("part_tags", tag_box);
76         part_tags_entry = Gtk::manage(new TagEntry(pool, ObjectType::PART, true));
77         part_tags_entry->show();
78         tag_box->pack_start(*part_tags_entry, true, true, 0);
79     }
80     x->get_widget("part_autofill", part_autofill_button);
81     x->get_widget("steps_grid", steps_grid);
82 
83     {
84         Gtk::Box *canvas_box;
85         GET_WIDGET(canvas_box);
86         canvas = Gtk::manage(new PreviewCanvas(pool, true));
87         canvas->show();
88         canvas->signal_size_allocate().connect([this](const auto &alloc) {
89             if (!(alloc == canvas_alloc)) {
90                 float pad = 1_mm;
91                 auto bb = pad_bbox(canvas->get_canvas().get_bbox(true), pad);
92                 canvas->get_canvas().zoom_to_bbox(bb);
93                 canvas_alloc = alloc;
94             }
95         });
96         canvas_box->pack_start(*canvas, true, true, 0);
97     }
98 
99     pads_lb->signal_selected_rows_changed().connect([this] {
100         std::set<UUID> pads_selected;
101         for (auto row : pads_lb->get_selected_rows()) {
102             auto ed = dynamic_cast<PadEditor *>(row->get_child());
103             for (auto pad : ed->get_pads()) {
104                 pads_selected.insert(pad->uuid);
105             }
106         }
107         auto &ca = canvas->get_canvas();
108         ca.set_flags_all(0, TriangleInfo::FLAG_HIGHLIGHT);
109         ca.set_highlight_enabled(pads_selected.size());
110         for (const auto &it : pads_selected) {
111             ObjectRef ref(ObjectType::PAD, it);
112             ca.set_flags(ref, TriangleInfo::FLAG_HIGHLIGHT, 0);
113         }
114     });
115 
116     entry_add_sanitizer(entity_name_entry);
117     entry_add_sanitizer(entity_prefix_entry);
118     entry_add_sanitizer(part_mpn_entry);
119     entry_add_sanitizer(part_value_entry);
120     entry_add_sanitizer(part_manufacturer_entry);
121     entry_add_sanitizer(part_description_entry);
122     entry_add_sanitizer(part_datasheet_entry);
123 
124     part_manufacturer_entry->set_completion(create_pool_manufacturer_completion(pool));
125 
126     part_mpn_entry->signal_changed().connect(sigc::mem_fun(*this, &PartWizard::update_can_finish));
127     entity_name_entry->signal_changed().connect(sigc::mem_fun(*this, &PartWizard::update_can_finish));
128     entity_prefix_entry->signal_changed().connect(sigc::mem_fun(*this, &PartWizard::update_can_finish));
129 
130     part_autofill_button->signal_clicked().connect(sigc::mem_fun(*this, &PartWizard::autofill));
131 
132     part_location_entry = pack_location_entry(x, "part_location_box");
133     part_location_entry->set_filename(Glib::build_filename(pool_base_path, "parts"));
134     part_location_entry->signal_changed().connect(sigc::mem_fun(*this, &PartWizard::update_can_finish));
135     {
136         Gtk::Button *from_part_button;
137         entity_location_entry = pack_location_entry(x, "entity_location_box", &from_part_button);
138         from_part_button->set_label("From part");
139         from_part_button->signal_clicked().connect([this] {
140             auto rel = get_rel_part_filename();
141             entity_location_entry->set_filename(Glib::build_filename(pool_base_path, "entities", rel));
142         });
143         entity_location_entry->set_filename(Glib::build_filename(pool_base_path, "entities"));
144         entity_location_entry->signal_changed().connect(sigc::mem_fun(*this, &PartWizard::update_can_finish));
145     }
146 
147     entity_name_from_mpn_button->signal_clicked().connect(
148             [this] { entity_name_entry->set_text(part_mpn_entry->get_text()); });
149 
150     entity_prefix_entry->set_text("U");
151 
152     sg_name = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
153 
154     gate_name_store = Gtk::ListStore::create(list_columns);
155 
156     {
157         Gtk::TreeModel::Row row = *(gate_name_store->append());
158         row[list_columns.name] = "Main";
159     }
160 
161     pads_lb->set_sort_func([](Gtk::ListBoxRow *a, Gtk::ListBoxRow *b) {
162         auto na = dynamic_cast<PadEditor *>(a->get_child())->names.front();
163         auto nb = dynamic_cast<PadEditor *>(b->get_child())->names.front();
164         return strcmp_natural(na, nb);
165     });
166 
167     part.entity = &entity;
168 
169     button_link_pads->signal_clicked().connect(sigc::mem_fun(*this, &PartWizard::handle_link));
170     button_unlink_pads->signal_clicked().connect(sigc::mem_fun(*this, &PartWizard::handle_unlink));
171     button_import_pads->signal_clicked().connect(sigc::mem_fun(*this, &PartWizard::handle_import));
172     button_next->signal_clicked().connect(sigc::mem_fun(*this, &PartWizard::handle_next));
173     button_back->signal_clicked().connect(sigc::mem_fun(*this, &PartWizard::handle_back));
174     button_select->signal_clicked().connect(sigc::mem_fun(*this, &PartWizard::handle_select));
175     button_finish->signal_clicked().connect(sigc::mem_fun(*this, &PartWizard::handle_finish));
176 
177     {
178         Gtk::Paned *page_package;
179         x->get_widget("page_package", page_package);
180         browser_package = Gtk::manage(new PoolBrowserPackage(pool));
181         browser_package->show();
182         page_package->add1(*browser_package);
183 
184         auto preview = Gtk::manage(new PreviewCanvas(pool, true));
185         button_select->set_sensitive(false);
186         browser_package->signal_selected().connect([this, preview] {
187             preview->load(ObjectType::PACKAGE, browser_package->get_selected());
188             button_select->set_sensitive(browser_package->get_selected());
189         });
190         browser_package->signal_activated().connect(sigc::mem_fun(*this, &PartWizard::handle_select));
191         browser_package->go_to(pkg_uuid);
192 
193         preview->show();
194         page_package->add2(*preview);
195     }
196 
197     set_mode(Mode::PACKAGE);
198 
199     signal_delete_event().connect([this](GdkEventAny *ev) {
200         if (processes.size()) {
201             Gtk::MessageDialog md(*this, "Can't close right now", false /* use_markup */, Gtk::MESSAGE_ERROR,
202                                   Gtk::BUTTONS_OK);
203             md.set_secondary_text("Close all running editors first");
204             md.run();
205             return true; // keep open
206         }
207         if (files_saved.size())
208             return false;
209 
210         if (mode == Mode::PACKAGE)
211             return false;
212 
213         Gtk::MessageDialog md(*this, "Really close?", false /* use_markup */, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE);
214         md.set_secondary_text(
215                 "By closing the part wizard, all changes to the new part will "
216                 "be lost");
217         md.add_button("Close and discard changes", 1);
218         md.add_button("Keep open", Gtk::RESPONSE_CANCEL);
219         switch (md.run()) {
220         case 1:
221             return false; // close
222 
223         default:
224             return true; // keep window open
225         }
226     });
227 }
228 
set_pkg(const Package * p)229 void PartWizard::set_pkg(const Package *p)
230 {
231     if (pkg)
232         return;
233     pkg = p;
234     part.attributes[Part::Attribute::MANUFACTURER] = {false, pkg->manufacturer};
235     part.package = pkg;
236     create_pad_editors();
237 }
238 
create_pad_editors()239 void PartWizard::create_pad_editors()
240 {
241     for (auto &it : pkg->pads) {
242         if (it.second.pool_padstack->type != Padstack::Type::MECHANICAL) {
243             auto ed = PadEditor::create(&it.second, this);
244             ed->pin_name_entry->signal_activate().connect([this, ed] {
245                 auto row = dynamic_cast<Gtk::ListBoxRow *>(ed->get_ancestor(GTK_TYPE_LIST_BOX_ROW));
246                 auto index = row->get_index();
247                 if (index >= 0) {
248                     if (auto nextrow = pads_lb->get_row_at_index(index + 1)) {
249                         auto ed_next = dynamic_cast<PadEditor *>(nextrow->get_child());
250                         ed_next->pin_name_entry->grab_focus();
251                     }
252                 }
253             });
254             ed->show_all();
255             pads_lb->append(*ed);
256             ed->unreference();
257         }
258     }
259 }
260 
set_mode(PartWizard::Mode mo)261 void PartWizard::set_mode(PartWizard::Mode mo)
262 {
263     if (mo == Mode::ASSIGN && processes.size() > 0)
264         return;
265 
266     if (mo == Mode::ASSIGN) {
267         stack->set_visible_child("assign");
268         header->set_subtitle("Assign pins to package " + pkg->name);
269     }
270     else if (mo == Mode::EDIT) {
271         prepare_edit();
272         stack->set_visible_child("edit");
273         update_can_finish();
274         header->set_subtitle("Part details & symbol");
275     }
276     else if (mo == Mode::PACKAGE) {
277         prepare_edit();
278         stack->set_visible_child("package");
279         header->set_subtitle("Select package");
280     }
281 
282 
283     button_back->set_visible(mo == Mode::EDIT);
284     button_finish->set_visible(mo == Mode::EDIT);
285     button_next->set_visible(mo == Mode::ASSIGN);
286     button_select->set_visible(mo == Mode::PACKAGE);
287     mode = mo;
288 }
289 
handle_next()290 void PartWizard::handle_next()
291 {
292     if (mode == Mode::ASSIGN) {
293         set_mode(Mode::EDIT);
294     }
295 }
296 
handle_back()297 void PartWizard::handle_back()
298 {
299     if (processes.size())
300         return;
301     set_mode(Mode::ASSIGN);
302 }
303 
handle_select()304 void PartWizard::handle_select()
305 {
306     auto p = browser_package->get_selected();
307     if (p) {
308         set_pkg(pool.get_package(p));
309         set_mode(Mode::ASSIGN);
310         canvas->load(ObjectType::PACKAGE, pkg->uuid);
311     }
312 }
313 
get_filenames()314 std::vector<std::string> PartWizard::get_filenames()
315 {
316     std::vector<std::string> filenames;
317     auto children = edit_left_box->get_children();
318     for (auto ch : children) {
319         if (auto ed = dynamic_cast<GateEditorWizard *>(ch)) {
320             auto unit_filename = ed->unit_location_entry->get_filename();
321             auto symbol_filename = ed->symbol_location_entry->get_filename();
322             filenames.push_back(unit_filename);
323             filenames.push_back(symbol_filename);
324         }
325     }
326     filenames.push_back(entity_location_entry->get_filename());
327     filenames.push_back(part_location_entry->get_filename());
328     return filenames;
329 }
330 
handle_finish()331 void PartWizard::handle_finish()
332 {
333     auto filenames = get_filenames();
334     std::vector<std::string> filenames_existing;
335     for (const auto &filename : filenames) {
336         if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) {
337             filenames_existing.push_back(filename);
338         }
339     }
340     if (filenames_existing.size() > 0) {
341         Gtk::MessageDialog md(*this, "Overwrite?", false /* use_markup */, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE);
342         std::string sec = "Creating this part will overwrite these files:\n";
343         for (const auto &fn : filenames_existing) {
344             sec += fn + "\n";
345         }
346         md.set_secondary_text(sec);
347         md.add_button("Overwrite", 1);
348         md.add_button("Cancel", Gtk::RESPONSE_CANCEL);
349         switch (md.run()) {
350         case 1:
351             // nop, go ahead
352             break;
353         default:
354             return;
355         }
356     }
357 
358 
359     try {
360         finish();
361         files_saved = filenames;
362         Gtk::Window::close();
363     }
364     catch (const std::exception &e) {
365         Gtk::MessageDialog md(*this, "Error Saving part", false /* use_markup */, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
366         md.set_secondary_text(e.what());
367         md.run();
368     }
369     catch (const Gio::Error &e) {
370         Gtk::MessageDialog md(*this, "Error Saving part", false /* use_markup */, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
371         md.set_secondary_text(e.what());
372         md.run();
373     }
374 }
375 
get_files_saved() const376 std::vector<std::string> PartWizard::get_files_saved() const
377 {
378     return files_saved;
379 }
380 
get_rel_part_filename()381 std::string PartWizard::get_rel_part_filename()
382 {
383     auto part_fn = Gio::File::create_for_path(part_location_entry->get_filename());
384     auto part_base = Gio::File::create_for_path(Glib::build_filename(pool_base_path, "parts"));
385     auto rel = part_base->get_relative_path(part_fn);
386     return rel;
387 }
388 
finish()389 void PartWizard::finish()
390 {
391     entity.name = entity_name_entry->get_text();
392     entity.prefix = entity_prefix_entry->get_text();
393     entity.tags = entity_tags_entry->get_tags();
394 
395     part.attributes[Part::Attribute::MPN] = {false, part_mpn_entry->get_text()};
396     part.attributes[Part::Attribute::VALUE] = {false, part_value_entry->get_text()};
397     part.attributes[Part::Attribute::MANUFACTURER] = {false, part_manufacturer_entry->get_text()};
398     part.attributes[Part::Attribute::DATASHEET] = {false, part_datasheet_entry->get_text()};
399     part.attributes[Part::Attribute::DESCRIPTION] = {false, part_description_entry->get_text()};
400     part.tags = part_tags_entry->get_tags();
401 
402     entity.manufacturer = part.get_manufacturer();
403 
404     std::vector<std::string> filenames;
405     filenames.push_back(entity_location_entry->get_filename());
406     filenames.push_back(part_location_entry->get_filename());
407 
408     auto children = edit_left_box->get_children();
409     for (auto ch : children) {
410         if (auto ed = dynamic_cast<GateEditorWizard *>(ch)) {
411             auto unit_filename = ed->unit_location_entry->get_filename();
412             auto symbol_filename = ed->symbol_location_entry->get_filename();
413             filenames.push_back(unit_filename);
414             filenames.push_back(symbol_filename);
415 
416             auto unit = &units.at(ed->gate->name);
417             assert(unit == ed->gate->unit);
418 
419             ed->gate->suffix = ed->suffix_entry->get_text();
420             unit->name = ed->unit_name_entry->get_text();
421             unit->manufacturer = part.get_manufacturer();
422         }
423     }
424 
425     for (const auto &it : filenames) {
426         if (!endswith(it, ".json")) {
427             throw std::runtime_error("Filename " + it + " doesn't end in .json");
428         }
429     }
430 
431     for (const auto &it : filenames) {
432         auto dir = Glib::path_get_dirname(it);
433         if (!Glib::file_test(dir, Glib::FILE_TEST_IS_DIR)) {
434             Gio::File::create_for_path(dir)->make_directory_with_parents();
435         }
436     }
437 
438     for (auto ch : children) {
439         if (auto ed = dynamic_cast<GateEditorWizard *>(ch)) {
440             auto unit_filename = ed->unit_location_entry->get_filename();
441             save_json_to_file(unit_filename, ed->gate->unit->serialize());
442 
443             auto symbol_filename_src = pool.get_tmp_filename(ObjectType::SYMBOL, symbols.at(ed->gate->unit->uuid));
444             auto symbol_filename_dest = ed->symbol_location_entry->get_filename();
445             auto sym = Symbol::new_from_file(symbol_filename_src, pool);
446             sym.name = ed->symbol_name_entry->get_text();
447             save_json_to_file(symbol_filename_dest, sym.serialize());
448         }
449     }
450     save_json_to_file(entity_location_entry->get_filename(), entity.serialize());
451     save_json_to_file(part_location_entry->get_filename(), part.serialize());
452 }
453 
handle_link()454 void PartWizard::handle_link()
455 {
456     auto rows = pads_lb->get_selected_rows();
457     std::deque<PadEditor *> editors;
458 
459     for (auto row : rows) {
460         auto ed = dynamic_cast<PadEditor *>(row->get_child());
461         editors.push_back(ed);
462     }
463     link_pads(editors);
464     update_pin_warnings();
465 }
466 
link_pads(const std::deque<PadEditor * > & editors)467 void PartWizard::link_pads(const std::deque<PadEditor *> &editors)
468 {
469     PadEditor *target = nullptr;
470     if (editors.size() < 2)
471         return;
472     for (auto ed : editors) {
473         if (ed->pads.size() > 1) {
474             target = ed;
475             break;
476         }
477     }
478     if (!target) {
479         for (auto &ed : editors) {
480             if (ed->pin_name_entry->get_text().size())
481                 target = ed;
482         }
483     }
484     if (!target) {
485         target = editors.front();
486     }
487     for (auto ed : editors) {
488         if (ed != target) {
489             target->pads.insert(ed->pads.begin(), ed->pads.end());
490         }
491     }
492     target->update_names();
493     for (auto ed : editors) {
494         if (ed != target) {
495             auto row = dynamic_cast<Gtk::ListBoxRow *>(ed->get_ancestor(GTK_TYPE_LIST_BOX_ROW));
496             delete row;
497         }
498     }
499     pads_lb->invalidate_sort();
500 }
501 
handle_unlink()502 void PartWizard::handle_unlink()
503 {
504     auto rows = pads_lb->get_selected_rows();
505     for (auto row : rows) {
506         auto ed = dynamic_cast<PadEditor *>(row->get_child());
507         if (ed->pads.size() > 1) {
508             auto pad_keep = *ed->pads.begin();
509             for (auto pad : ed->pads) {
510                 if (pad != pad_keep) {
511                     auto ed_new = PadEditor::create(pad, this);
512                     ed_new->show_all();
513                     pads_lb->append(*ed_new);
514                     ed_new->unreference();
515                     ed_new->pin_name_entry->set_text(ed->pin_name_entry->get_text());
516                     ed_new->dir_combo->set_active_id(ed->dir_combo->get_active_id());
517                     ed_new->combo_gate_entry->set_text(ed->combo_gate_entry->get_text());
518                     ed_new->update_names();
519                 }
520             }
521 
522             ed->pads = {pad_keep};
523             ed->update_names();
524         }
525     }
526     pads_lb->invalidate_sort();
527 }
528 
handle_import()529 void PartWizard::handle_import()
530 {
531     GtkFileChooserNative *native =
532             gtk_file_chooser_native_new("Open", gobj(), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open", "_Cancel");
533     auto chooser = Glib::wrap(GTK_FILE_CHOOSER(native));
534     auto filter = Gtk::FileFilter::create();
535     filter->set_name("CSV documents");
536     filter->add_pattern("*.csv");
537     chooser->add_filter(filter);
538     filter = Gtk::FileFilter::create();
539     filter->set_name("json documents");
540     filter->add_pattern("*.json");
541     chooser->add_filter(filter);
542 
543     if (gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)) == GTK_RESPONSE_ACCEPT) {
544         auto filename = chooser->get_filename();
545         try {
546             auto ifs = make_ifstream(filename);
547             if (!ifs.is_open()) {
548                 throw std::runtime_error("file " + filename + " not opened");
549             }
550             if (endswith(filename, ".json")) {
551                 json j;
552                 ifs >> j;
553                 ifs.close();
554                 import_pads(j);
555             }
556             else {
557                 CSV::Csv csv;
558                 ifs >> csv;
559                 ifs.close();
560                 import_pads(csv);
561             }
562         }
563         catch (const std::exception &e) {
564             Gtk::MessageDialog md(*this, "Error importing", false /* use_markup */, Gtk::MESSAGE_ERROR,
565                                   Gtk::BUTTONS_OK);
566             md.set_secondary_text(e.what());
567             md.run();
568         }
569         catch (...) {
570             Gtk::MessageDialog md(*this, "Error importing", false /* use_markup */, Gtk::MESSAGE_ERROR,
571                                   Gtk::BUTTONS_OK);
572             md.set_secondary_text("unknown error");
573             md.run();
574         }
575     }
576 }
577 
import_pads(CSV::Csv & csv)578 void PartWizard::import_pads(CSV::Csv &csv)
579 {
580     std::map<std::string, PadImportItem> items;
581     /* Expand number of fields for safe and easy access. */
582     csv.expand(4);
583     for (auto &line : csv) {
584         std::string pad_name = line[0];
585         std::string pin_name = line[1];
586         std::string dir = line[2];
587         std::string gate_name = line[3];
588         trim(pad_name);
589         if (pad_name.empty()) {
590             continue;
591         }
592         auto &item = items[pad_name];
593         item.pin = pin_name;
594         trim(dir);
595         for (auto &c : dir) {
596             c = tolower(c);
597             if (c == ' ') {
598                 c = '_';
599             }
600         }
601         item.direction = Pin::direction_lut.lookup(dir, item.direction);
602         if (!gate_name.empty()) {
603             item.gate = gate_name;
604         }
605         if (line.size() >= 5) {
606             for (size_t i = 4; i < line.size(); i++) {
607                 std::string alt = line[i];
608                 trim(alt);
609                 if (alt.size()) {
610                     item.alt.push_back(alt);
611                 }
612             }
613         }
614     }
615     import_pads(items);
616 }
617 
import_pads(const json & j)618 void PartWizard::import_pads(const json &j)
619 {
620     std::map<std::string, PadImportItem> items;
621     for (auto it = j.cbegin(); it != j.cend(); ++it) {
622         std::string pad_name = it.key();
623         const json &v = it.value();
624         auto &item = items[pad_name];
625         item.pin = v.value("pin", "");
626         item.gate = v.value("gate", "Main");
627         item.direction = Pin::direction_lut.lookup(v.value("direction", ""), Pin::Direction::INPUT);
628         if (v.count("alt")) {
629             for (const auto &a : v.at("alt")) {
630                 item.alt.push_back(a.get<std::string>());
631             }
632         }
633     }
634     import_pads(items);
635 }
636 
import_pads(const std::map<std::string,PadImportItem> & items)637 void PartWizard::import_pads(const std::map<std::string, PadImportItem> &items)
638 {
639     auto chs = pads_lb->get_children();
640     for (auto ch : chs) {
641         delete ch;
642     }
643     create_pad_editors();
644     frozen = true;
645     for (auto &ch : pads_lb->get_children()) {
646         auto ed = dynamic_cast<PadEditor *>(dynamic_cast<Gtk::ListBoxRow *>(ch)->get_child());
647         auto pad_name = (*ed->pads.begin())->name;
648         if (items.count(pad_name)) {
649             const auto &it = items.at(pad_name);
650             std::string pin_name = it.pin;
651             std::string gate_name = it.gate;
652             trim(pin_name);
653             trim(gate_name);
654             ed->pin_name_entry->set_text(pin_name);
655             ed->dir_combo->set_active_id(std::to_string(static_cast<int>(it.direction)));
656             ed->combo_gate_entry->set_text(gate_name);
657             {
658                 std::stringstream ss;
659                 for (auto &it2 : it.alt) {
660                     ss << it2 << " ";
661                 }
662                 ed->pin_names_entry->set_text(ss.str());
663             }
664         }
665     }
666     autolink_pads();
667     frozen = false;
668     update_gate_names();
669     update_pin_warnings();
670 }
671 
autolink_pads()672 void PartWizard::autolink_pads()
673 {
674     auto pin_names = get_pin_names();
675     for (const auto &it : pin_names) {
676         if (it.second.size() > 1) {
677             std::deque<PadEditor *> pads(it.second.begin(), it.second.end());
678             link_pads(pads);
679         }
680     }
681 }
682 
update_gate_names()683 void PartWizard::update_gate_names()
684 {
685     if (frozen)
686         return;
687     std::set<std::string> names;
688     names.insert("Main");
689     for (auto &ch : pads_lb->get_children()) {
690         auto ed = dynamic_cast<PadEditor *>(dynamic_cast<Gtk::ListBoxRow *>(ch)->get_child());
691         auto name = ed->get_gate_name();
692         if (name.size()) {
693             names.insert(name);
694         }
695     }
696     std::vector<std::string> names_sorted(names.begin(), names.end());
697     std::sort(names_sorted.begin(), names_sorted.end());
698     gate_name_store->freeze_notify();
699     gate_name_store->clear();
700     for (const auto &name : names_sorted) {
701         Gtk::TreeModel::Row row = *(gate_name_store->append());
702         row[list_columns.name] = name;
703     }
704     gate_name_store->thaw_notify();
705 }
706 
prepare_edit()707 void PartWizard::prepare_edit()
708 {
709     std::set<Gate *> gates_avail;
710     update_part();
711     auto children = edit_left_box->get_children();
712     for (auto ch : children) {
713         if (auto ed = dynamic_cast<GateEditorWizard *>(ch)) {
714             if (!entity.gates.count(ed->gate.uuid)) {
715                 delete ed;
716             }
717             else {
718                 std::cout << "found gate " << (std::string)ed->gate->uuid << std::endl;
719                 gates_avail.insert(ed->gate);
720             }
721         }
722     }
723 
724 
725     for (auto &it : entity.gates) {
726         if (!gates_avail.count(&it.second)) {
727             auto ed = GateEditorWizard::create(&it.second, this);
728             edit_left_box->pack_start(*ed, false, false, 0);
729             ed->show_all();
730             ed->edit_symbol_button->signal_clicked().connect([this, ed] {
731                 auto symbol_uuid = symbols.at(ed->gate->unit->uuid);
732                 auto symbol_filename = pool.get_tmp_filename(ObjectType::SYMBOL, symbol_uuid);
733                 {
734                     auto sym = Symbol::new_from_file(symbol_filename, pool);
735                     sym.name = ed->symbol_name_entry->get_text();
736                     save_json_to_file(symbol_filename, sym.serialize());
737                 }
738 
739                 auto proc = appwin.spawn(PoolProjectManagerProcess::Type::IMP_SYMBOL, {symbol_filename});
740                 processes.emplace(symbol_filename, proc);
741                 symbols_open.emplace(symbol_uuid);
742                 proc->signal_exited().connect([this, symbol_filename, symbol_uuid, ed](int status, bool modified) {
743                     processes.erase(symbol_filename);
744                     symbols_open.erase(symbol_uuid);
745                     {
746                         auto sym = Symbol::new_from_file(symbol_filename, pool);
747                         ed->symbol_name_entry->set_text(sym.name);
748                     }
749                     update_can_finish();
750                     update_symbol_pins_mapped();
751                 });
752                 update_can_finish();
753             });
754 
755             ed->unreference();
756         }
757     }
758 
759     update_symbol_pins_mapped();
760 }
761 
get_pin_names()762 std::map<std::pair<std::string, std::string>, std::set<class PadEditor *>> PartWizard::get_pin_names()
763 {
764     std::map<std::pair<std::string, std::string>, std::set<class PadEditor *>> pin_names;
765     for (auto &ch : pads_lb->get_children()) {
766         auto ed = dynamic_cast<PadEditor *>(dynamic_cast<Gtk::ListBoxRow *>(ch)->get_child());
767         std::pair<std::string, std::string> key(ed->combo_gate_entry->get_text(), ed->pin_name_entry->get_text());
768         if (key.second.size()) {
769             pin_names[key];
770             pin_names[key].insert(ed);
771         }
772     }
773     return pin_names;
774 }
775 
update_pin_warnings()776 void PartWizard::update_pin_warnings()
777 {
778     if (frozen)
779         return;
780     auto pin_names = get_pin_names();
781     bool has_warning = pin_names.size() == 0;
782     for (auto &it : pin_names) {
783         std::string icon_name;
784         if (it.second.size() > 1) {
785             icon_name = "dialog-warning-symbolic";
786             has_warning = true;
787         }
788         for (auto ed : it.second) {
789             ed->pin_name_entry->set_icon_from_icon_name(icon_name, Gtk::ENTRY_ICON_SECONDARY);
790         }
791     }
792     button_next->set_sensitive(!has_warning);
793 }
794 
update_part()795 void PartWizard::update_part()
796 {
797     std::cout << "upd part" << std::endl;
798     std::set<UUID> pins_used;
799     std::set<UUID> units_used;
800     part.pad_map.clear();
801     for (auto &ch : pads_lb->get_children()) {
802         auto ed = dynamic_cast<PadEditor *>(dynamic_cast<Gtk::ListBoxRow *>(ch)->get_child());
803         std::string pin_name = ed->pin_name_entry->get_text();
804         std::string gate_name = ed->combo_gate_entry->get_text();
805         if (pin_name.size()) {
806             if (!units.count(gate_name)) {
807                 auto uu = UUID::random();
808                 units.emplace(gate_name, uu);
809             }
810             Unit *u = &units.at(gate_name);
811             units_used.insert(u->uuid);
812             Pin *pin = nullptr;
813             {
814                 auto pi = std::find_if(u->pins.begin(), u->pins.end(),
815                                        [pin_name](const auto &a) { return a.second.primary_name == pin_name; });
816                 if (pi != u->pins.end()) {
817                     pin = &pi->second;
818                 }
819             }
820             if (!pin) {
821                 auto uu = UUID::random();
822                 pin = &u->pins.emplace(uu, uu).first->second;
823             }
824             pins_used.insert(pin->uuid);
825             pin->primary_name = pin_name;
826             pin->direction = static_cast<Pin::Direction>(std::stoi(ed->dir_combo->get_active_id()));
827             {
828                 std::stringstream ss(ed->pin_names_entry->get_text());
829                 std::istream_iterator<std::string> begin(ss);
830                 std::istream_iterator<std::string> end;
831                 std::vector<std::string> tags(begin, end);
832                 pin->names = tags;
833             }
834 
835             Gate *gate = nullptr;
836             {
837                 auto gi = std::find_if(entity.gates.begin(), entity.gates.end(),
838                                        [gate_name](const auto &a) { return a.second.name == gate_name; });
839                 if (gi != entity.gates.end()) {
840                     gate = &gi->second;
841                 }
842             }
843             if (!gate) {
844                 auto uu = UUID::random();
845                 gate = &entity.gates.emplace(uu, uu).first->second;
846             }
847             gate->name = gate_name;
848             gate->unit = &units.at(gate_name);
849 
850             for (auto pad : ed->pads) {
851                 part.pad_map.emplace(std::piecewise_construct, std::forward_as_tuple(pad->uuid),
852                                      std::forward_as_tuple(gate, pin));
853             }
854         }
855     }
856 
857     map_erase_if(entity.gates, [units_used](auto x) { return units_used.count(x.second.unit->uuid) == 0; });
858 
859     for (auto it = units.begin(); it != units.end();) {
860         auto uu = it->second.uuid;
861         if (units_used.count(uu) == 0) {
862             std::cout << "del sym" << std::endl;
863 
864             {
865                 auto unit_filename = pool.get_tmp_filename(ObjectType::UNIT, uu);
866                 auto fi = Gio::File::create_for_path(unit_filename);
867                 fi->remove();
868             }
869 
870             {
871                 auto sym_filename = pool.get_tmp_filename(ObjectType::SYMBOL, symbols.at(uu));
872                 auto fi = Gio::File::create_for_path(sym_filename);
873                 fi->remove();
874                 symbols.erase(uu);
875             }
876             units.erase(it++);
877         }
878         else {
879             it++;
880         }
881     }
882     for (auto &it : units) {
883         map_erase_if(it.second.pins, [pins_used](auto x) { return pins_used.count(x.second.uuid) == 0; });
884     }
885 
886 
887     for (const auto &it : units) {
888         if (!symbols.count(it.second.uuid)) {
889             Symbol sym(UUID::random());
890             sym.unit = &it.second;
891             sym.name = "edit me";
892             auto filename = pool.get_tmp_filename(ObjectType::SYMBOL, sym.uuid);
893             save_json_to_file(filename, sym.serialize());
894             symbols.emplace(sym.unit->uuid, sym.uuid);
895         }
896         auto filename = pool.get_tmp_filename(ObjectType::UNIT, it.second.uuid);
897         save_json_to_file(filename, it.second.serialize());
898     }
899 }
900 
update_symbol_pins_mapped()901 void PartWizard::update_symbol_pins_mapped()
902 {
903     for (const auto &it : symbols) {
904         const auto &unit_uu = it.first;
905         auto symbol_filename = pool.get_tmp_filename(ObjectType::SYMBOL, it.second);
906         auto sym = Symbol::new_from_file(symbol_filename, pool);
907         auto n_pins = sym.pins.size();
908         symbol_pins_mapped[unit_uu] = n_pins;
909     }
910 
911     auto children = edit_left_box->get_children();
912     for (auto ch : children) {
913         if (auto ed = dynamic_cast<GateEditorWizard *>(ch)) {
914             const auto &unit_uu = ed->gate->unit->uuid;
915             ed->update_symbol_pins(symbol_pins_mapped.at(unit_uu));
916         }
917     }
918     update_steps();
919 }
920 
autofill()921 void PartWizard::autofill()
922 {
923     entity_name_entry->set_text(part_mpn_entry->get_text());
924     auto rel = get_rel_part_filename();
925     entity_location_entry->set_filename(Glib::build_filename(pool_base_path, "entities", rel));
926     auto children = edit_left_box->get_children();
927     for (auto ch : children) {
928         if (auto ed = dynamic_cast<GateEditorWizard *>(ch)) {
929             std::string suffix = ed->suffix_entry->get_text();
930             trim(suffix);
931             if (suffix.size()) {
932                 auto txt = part_mpn_entry->get_text() + " " + suffix;
933                 ed->unit_name_entry->set_text(txt);
934                 if (ed->symbol_name_entry->get_sensitive())
935                     ed->symbol_name_entry->set_text(txt);
936             }
937             else {
938                 auto txt = part_mpn_entry->get_text();
939                 ed->unit_name_entry->set_text(txt);
940                 if (ed->symbol_name_entry->get_sensitive())
941                     ed->symbol_name_entry->set_text(txt);
942             }
943             ed->unit_location_entry->set_filename(
944                     Glib::build_filename(pool_base_path, "units", ed->get_suffixed_filename_from_part()));
945             ed->symbol_location_entry->set_filename(
946                     Glib::build_filename(pool_base_path, "symbols", ed->get_suffixed_filename_from_part()));
947         }
948     }
949 }
950 
reload()951 void PartWizard::reload()
952 {
953     part.package = pool.get_package(part.package.uuid);
954 }
955 
update_can_finish()956 void PartWizard::update_can_finish()
957 {
958     bool editors_open = processes.size() > 0;
959     button_back->set_sensitive(!editors_open);
960     valid = true;
961 
962     auto check_entry_not_empty = [this](Gtk::Entry *e, const std::string &msg, bool *v = nullptr) {
963         std::string t = e->get_text();
964         trim(t);
965         if (!t.size()) {
966             entry_set_warning(e, msg);
967             valid = false;
968         }
969         else {
970             entry_set_warning(e, "");
971         }
972         if (v)
973             *v = t.size();
974     };
975 
976     check_entry_not_empty(part_mpn_entry, "MPN is empty", &mpn_valid);
977     check_entry_not_empty(entity_name_entry, "Entity name is empty");
978     check_entry_not_empty(entity_prefix_entry, "Entity prefix is empty");
979     valid = part_location_entry->check_ends_json(&part_filename_valid) && valid;
980 
981     std::set<std::string> symbol_filenames;
982     std::set<std::string> unit_filenames;
983     std::set<std::string> suffixes;
984     std::set<std::string> unit_names;
985 
986     gates_valid = true;
987     int n_gates = entity.gates.size();
988 
989     auto children = edit_left_box->get_children();
990     for (auto ch : children) {
991         if (auto ed = dynamic_cast<GateEditorWizard *>(ch)) {
992             ed->unit_location_entry->set_warning("");
993             ed->symbol_location_entry->set_warning("");
994             entry_set_warning(ed->suffix_entry, "");
995             entry_set_warning(ed->unit_name_entry, "");
996 
997             valid = ed->unit_location_entry->check_ends_json() && valid;
998             valid = ed->symbol_location_entry->check_ends_json() && valid;
999 
1000             check_entry_not_empty(ed->unit_name_entry, "Unit name is empty");
1001             std::string unit_filename = ed->unit_location_entry->get_filename();
1002             trim(unit_filename);
1003 
1004             if (!unit_filenames.insert(unit_filename).second) {
1005                 ed->unit_location_entry->set_warning("Duplicate unit filename");
1006                 valid = false;
1007             }
1008 
1009             std::string symbol_filename = ed->symbol_location_entry->get_filename();
1010             trim(symbol_filename);
1011 
1012             if (!symbol_filenames.insert(symbol_filename).second) {
1013                 ed->symbol_location_entry->set_warning("Duplicate symbol filename");
1014                 valid = false;
1015             }
1016 
1017             std::string suffix = ed->suffix_entry->get_text();
1018             trim(suffix);
1019             if (!suffixes.insert(suffix).second) {
1020                 entry_set_warning(ed->suffix_entry, "Duplicate unit suffix");
1021                 valid = false;
1022                 gates_valid = false;
1023             }
1024             if (suffix.size() == 0 && (n_gates > 1)) {
1025                 entry_set_warning(ed->suffix_entry, "Unit suffix is empty");
1026                 valid = false;
1027                 gates_valid = false;
1028             }
1029 
1030             std::string unit_name = ed->unit_name_entry->get_text();
1031             trim(unit_name);
1032             if (!unit_names.insert(unit_name).second) {
1033                 entry_set_warning(ed->unit_name_entry, "Duplicate unit name");
1034                 valid = false;
1035             }
1036 
1037             ed->set_can_edit_symbol_name(symbols_open.count(symbols.at(ed->gate->unit->uuid)) == 0);
1038         }
1039     }
1040     update_steps();
1041     button_finish->set_sensitive(!editors_open && valid);
1042 }
1043 
update_steps()1044 void PartWizard::update_steps()
1045 {
1046     auto chs = steps_grid->get_children();
1047     for (auto ch : chs) {
1048         delete ch;
1049     }
1050     int top = 0;
1051     auto add_step = [this, &top](const std::string &t, int st) {
1052         auto la = Gtk::manage(new Gtk::Label(t));
1053         la->set_halign(Gtk::ALIGN_START);
1054         steps_grid->attach(*la, 1, top, 1, 1);
1055 
1056         auto im = Gtk::manage(new Gtk::Image());
1057         if (st == 1) {
1058             im->set_from_icon_name("pan-end-symbolic", Gtk::ICON_SIZE_BUTTON);
1059         }
1060         else if (st == 2) {
1061             im->set_from_icon_name("object-select-symbolic", Gtk::ICON_SIZE_BUTTON);
1062         }
1063         steps_grid->attach(*im, 0, top, 1, 1);
1064 
1065         top++;
1066     };
1067 
1068     int progress = 0;
1069 
1070     auto compare_progress = [&progress](int x) {
1071         if (x == progress)
1072             return 1;
1073         else if (x < progress)
1074             return 2;
1075         return 0;
1076     };
1077 
1078     bool all_symbol_pins_mapped = true;
1079     for (const auto &it : units) {
1080         unsigned int n_mapped = 0;
1081         if (symbol_pins_mapped.count(it.second.uuid)) {
1082             n_mapped = symbol_pins_mapped.at(it.second.uuid);
1083         }
1084         if (n_mapped < it.second.pins.size()) {
1085             all_symbol_pins_mapped = false;
1086             break;
1087         }
1088     }
1089 
1090     if (entity.gates.size() == 1) {
1091         if (mpn_valid) {
1092             progress = 1;
1093             if (part_filename_valid) {
1094                 progress = 2;
1095                 if (valid) {
1096                     progress = 3;
1097                     if (all_symbol_pins_mapped) {
1098                         progress = 4;
1099                     }
1100                 }
1101             }
1102         }
1103         add_step("Enter MPN (and Manufacturer)", compare_progress(0));
1104         add_step("Enter part filename", compare_progress(1));
1105         add_step("Press Autofill", compare_progress(2));
1106         add_step("Edit Symbol", compare_progress(3));
1107     }
1108     else {
1109         if (mpn_valid) {
1110             progress = 1;
1111             if (part_filename_valid) {
1112                 progress = 2;
1113                 if (gates_valid) {
1114                     progress = 3;
1115                     if (valid) {
1116                         progress = 4;
1117                         if (all_symbol_pins_mapped) {
1118                             progress = 5;
1119                         }
1120                     }
1121                 }
1122             }
1123         }
1124         add_step("Enter MPN (and Manufacturer)", compare_progress(0));
1125         add_step("Enter Part filename", compare_progress(1));
1126         add_step("Enter Gate suffixes", compare_progress(2));
1127         add_step("Press Autofill", compare_progress(3));
1128         add_step("Edit Symbols", compare_progress(4));
1129     }
1130 
1131 
1132     steps_grid->show_all();
1133 }
1134 
create(const UUID & pkg_uuid,const std::string & bp,class Pool & po,class PoolProjectManagerAppWindow & aw)1135 PartWizard *PartWizard::create(const UUID &pkg_uuid, const std::string &bp, class Pool &po,
1136                                class PoolProjectManagerAppWindow &aw)
1137 {
1138     PartWizard *w;
1139     Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
1140     x->add_from_resource(
1141             "/org/horizon-eda/horizon/pool-prj-mgr/pool-mgr/part_wizard/"
1142             "part_wizard.ui");
1143     x->get_widget_derived("part_wizard", w, pkg_uuid, bp, po, aw);
1144     return w;
1145 }
1146 
~PartWizard()1147 PartWizard::~PartWizard()
1148 {
1149     for (auto &it : units) {
1150         auto filename = pool.get_tmp_filename(ObjectType::UNIT, it.second.uuid);
1151         Gio::File::create_for_path(filename)->remove();
1152     }
1153     for (auto &it : symbols) {
1154         auto filename = pool.get_tmp_filename(ObjectType::SYMBOL, it.second);
1155         Gio::File::create_for_path(filename)->remove();
1156     }
1157 }
1158 } // namespace horizon
1159