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