1 #include "part_browser_window.hpp"
2 #include "util/util.hpp"
3 #include "widgets/part_preview.hpp"
4 #include "widgets/pool_browser_part.hpp"
5 #include "widgets/pool_browser_parametric.hpp"
6 #include "util/stock_info_provider.hpp"
7 #include "preferences/preferences_provider.hpp"
8 #include "util/win32_undef.hpp"
9 #include "preferences/preferences.hpp"
10 #include "util/gtk_util.hpp"
11 #include "pool/pool_cache_status.hpp"
12
13 namespace horizon {
14
header_fun(Gtk::ListBoxRow * row,Gtk::ListBoxRow * before)15 static void header_fun(Gtk::ListBoxRow *row, Gtk::ListBoxRow *before)
16 {
17 if (before && !row->get_header()) {
18 auto ret = Gtk::manage(new Gtk::Separator);
19 row->set_header(*ret);
20 }
21 }
22
23 class UUIDBox : public Gtk::Box {
24 public:
25 using Gtk::Box::Box;
26 UUID uuid;
27 };
28
PartBrowserWindow(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,const std::string & pool_path,std::deque<UUID> & favs)29 PartBrowserWindow::PartBrowserWindow(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x,
30 const std::string &pool_path, std::deque<UUID> &favs)
31 : Gtk::Window(cobject), pool(pool_path), pool_parametric(pool_path), favorites(favs),
32 state_store(this, "part-browser")
33 {
34 x->get_widget("notebook", notebook);
35 x->get_widget("menu1", add_search_menu);
36 x->get_widget("place_part_button", place_part_button);
37 x->get_widget("assign_part_button", assign_part_button);
38 x->get_widget("fav_button", fav_button);
39 x->get_widget("lb_favorites", lb_favorites);
40 x->get_widget("lb_recent", lb_recent);
41 x->get_widget("out_of_date_info_bar", out_of_date_info_bar);
42 info_bar_hide(out_of_date_info_bar);
43
44 lb_favorites->set_header_func(sigc::ptr_fun(header_fun));
45 lb_recent->set_header_func(sigc::ptr_fun(header_fun));
46 lb_favorites->signal_row_selected().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_favorites_selected));
47 lb_favorites->signal_row_activated().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_favorites_activated));
48 lb_recent->signal_row_selected().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_favorites_selected));
49 lb_recent->signal_row_activated().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_favorites_activated));
50
51 {
52 Gtk::Button *pool_cache_button;
53 x->get_widget("pool_cache_button", pool_cache_button);
54 pool_cache_button->signal_clicked().connect(
55 [this] { s_signal_open_pool_cache_window.emit(items_out_of_date); });
56 }
57
58 {
59 auto la = Gtk::manage(new Gtk::MenuItem("MPN Search"));
60 la->signal_activate().connect([this] {
61 auto ch = add_search();
62 ch->search_once();
63 });
64 la->show();
65 add_search_menu->append(*la);
66 }
67 for (const auto &it : pool_parametric.get_tables()) {
68 auto la = Gtk::manage(new Gtk::MenuItem(it.second.display_name));
69 std::string table_name = it.first;
70 la->signal_activate().connect([this, table_name] {
71 auto ch = add_search_parametric(table_name);
72 ch->search_once();
73 });
74 la->show();
75 add_search_menu->append(*la);
76 }
77 fav_toggled_conn =
78 fav_button->signal_toggled().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_fav_toggled));
79 place_part_button->signal_clicked().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_place_part));
80 assign_part_button->signal_clicked().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_assign_part));
81
82 preview = Gtk::manage(new PartPreview(pool, false, "part_browser"));
83 {
84 Gtk::Box *box;
85 x->get_widget("box", box);
86 box->pack_start(*preview, true, true, 0);
87 }
88 preview->show();
89
90 update_part_current();
91 update_favorites();
92
93 auto ch_search = add_search();
94 for (const auto &it : pool_parametric.get_tables()) {
95 add_search_parametric(it.first);
96 }
97 notebook->set_current_page(notebook->page_num(*ch_search));
98 signal_show().connect(sigc::track_obj([ch_search] { ch_search->search_once(); }, *ch_search));
99 signal_show().connect([this] {
100 if (needs_reload) {
101 needs_reload = false;
102 reload();
103 }
104 });
105
106 notebook->signal_switch_page().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_switch_page));
107
108 {
109 Gtk::Paned *paned;
110 GET_WIDGET(paned);
111 paned_state_store.emplace(paned, "part_browser");
112 }
113 }
114
reload()115 void PartBrowserWindow::reload()
116 {
117 pool.clear();
118 auto current_page = notebook->get_nth_page(notebook->get_current_page());
119 for (auto page : notebook->get_children()) {
120 if (auto ch = dynamic_cast<PoolBrowser *>(page)) {
121 ch->reload_pools();
122 if (current_page == ch)
123 ch->search();
124 else
125 ch->clear_search_once();
126 }
127 }
128 }
129
pool_updated(const std::string & p)130 void PartBrowserWindow::pool_updated(const std::string &p)
131 {
132 for (const auto &[it_path, it_uu] : pool.get_actually_included_pools(true)) {
133 if (it_path == p) {
134 if (!is_visible()) {
135 needs_reload = true;
136 return;
137 }
138
139 pool_updated_conn.disconnect();
140 // debounce since we might receive multiple updated signals in short succession
141 pool_updated_conn = Glib::signal_timeout().connect(sigc::track_obj(
142 [this] {
143 reload();
144 return false;
145 },
146 *this),
147 500);
148
149 return;
150 }
151 }
152 }
153
handle_favorites_selected(Gtk::ListBoxRow * row)154 void PartBrowserWindow::handle_favorites_selected(Gtk::ListBoxRow *row)
155 {
156 update_part_current();
157 }
158
handle_favorites_activated(Gtk::ListBoxRow * row)159 void PartBrowserWindow::handle_favorites_activated(Gtk::ListBoxRow *row)
160 {
161 handle_place_part();
162 }
163
handle_place_part()164 void PartBrowserWindow::handle_place_part()
165 {
166 if (part_current)
167 s_signal_place_part.emit(part_current);
168 }
169
handle_assign_part()170 void PartBrowserWindow::handle_assign_part()
171 {
172 if (part_current)
173 s_signal_assign_part.emit(part_current);
174 }
175
handle_fav_toggled()176 void PartBrowserWindow::handle_fav_toggled()
177 {
178 if (part_current) {
179 if (fav_button->get_active()) {
180 assert(std::count(favorites.begin(), favorites.end(), part_current) == 0);
181 favorites.push_front(part_current);
182 }
183 else {
184 assert(std::count(favorites.begin(), favorites.end(), part_current) == 1);
185 auto it = std::find(favorites.begin(), favorites.end(), part_current);
186 favorites.erase(it);
187 }
188 update_favorites();
189 }
190 }
191
handle_switch_page(Gtk::Widget * page,guint index)192 void PartBrowserWindow::handle_switch_page(Gtk::Widget *page, guint index)
193 {
194 update_part_current();
195 if (notebook->in_destruction())
196 return;
197 if (auto ch = dynamic_cast<PoolBrowser *>(page))
198 ch->search_once();
199 }
200
placed_part(const UUID & uu)201 void PartBrowserWindow::placed_part(const UUID &uu)
202 {
203 auto ncount = std::count(recents.begin(), recents.end(), uu);
204 assert(ncount < 2);
205 if (ncount) {
206 auto it = std::find(recents.begin(), recents.end(), uu);
207 recents.erase(it);
208 }
209 recents.push_front(uu);
210 update_recents();
211 }
212
go_to_part(const UUID & uu)213 void PartBrowserWindow::go_to_part(const UUID &uu)
214 {
215 auto page = notebook->get_nth_page(notebook->get_current_page());
216 auto br = dynamic_cast<PoolBrowserPart *>(page);
217 if (br)
218 br->go_to(uu);
219 else {
220 auto br_new = add_search(uu);
221 br_new->search_once();
222 }
223 }
224
update_favorites()225 void PartBrowserWindow::update_favorites()
226 {
227 auto children = lb_favorites->get_children();
228 for (auto it : children) {
229 delete it;
230 }
231
232 for (const auto &it : favorites) {
233 const Part *part = nullptr;
234 try {
235 part = pool.get_part(it);
236 }
237 catch (const std::runtime_error &e) {
238 part = nullptr;
239 }
240 if (part) {
241 auto box = Gtk::manage(new UUIDBox(Gtk::ORIENTATION_VERTICAL, 4));
242 box->uuid = it;
243 auto la_MPN = Gtk::manage(new Gtk::Label());
244 la_MPN->set_xalign(0);
245 la_MPN->set_markup("<b>" + part->get_MPN() + "</b>");
246 box->pack_start(*la_MPN, false, false, 0);
247
248 auto la_mfr = Gtk::manage(new Gtk::Label());
249 la_mfr->set_xalign(0);
250 la_mfr->set_text(part->get_manufacturer());
251 box->pack_start(*la_mfr, false, false, 0);
252
253 box->set_margin_top(4);
254 box->set_margin_bottom(4);
255 box->set_margin_start(4);
256 box->set_margin_end(4);
257 lb_favorites->append(*box);
258 box->show_all();
259 }
260 }
261 }
262
update_recents()263 void PartBrowserWindow::update_recents()
264 {
265 auto children = lb_recent->get_children();
266 for (auto it : children) {
267 delete it;
268 }
269
270 for (const auto &it : recents) {
271 auto part = pool.get_part(it);
272 if (part) {
273 auto box = Gtk::manage(new UUIDBox(Gtk::ORIENTATION_VERTICAL, 4));
274 box->uuid = it;
275 auto la_MPN = Gtk::manage(new Gtk::Label());
276 la_MPN->set_xalign(0);
277 la_MPN->set_markup("<b>" + part->get_MPN() + "</b>");
278 box->pack_start(*la_MPN, false, false, 0);
279
280 auto la_mfr = Gtk::manage(new Gtk::Label());
281 la_mfr->set_xalign(0);
282 la_mfr->set_text(part->get_manufacturer());
283 box->pack_start(*la_mfr, false, false, 0);
284
285 box->set_margin_top(4);
286 box->set_margin_bottom(4);
287 box->set_margin_start(4);
288 box->set_margin_end(4);
289 lb_recent->append(*box);
290 box->show_all();
291 }
292 }
293 }
294
update_part_current()295 void PartBrowserWindow::update_part_current()
296 {
297 if (in_destruction())
298 return;
299 auto page = notebook->get_nth_page(notebook->get_current_page());
300 SelectionProvider *prv = nullptr;
301 prv = dynamic_cast<SelectionProvider *>(page);
302
303 if (prv) {
304 part_current = prv->get_selected();
305 }
306 else {
307 if (page->get_name() == "fav") {
308 auto row = lb_favorites->get_selected_row();
309 if (row) {
310 part_current = dynamic_cast<UUIDBox *>(row->get_child())->uuid;
311 }
312 else {
313 part_current = UUID();
314 }
315 }
316 else if (page->get_name() == "recent") {
317 auto row = lb_recent->get_selected_row();
318 if (row) {
319 part_current = dynamic_cast<UUIDBox *>(row->get_child())->uuid;
320 }
321 else {
322 part_current = UUID();
323 }
324 }
325 else {
326 part_current = UUID();
327 }
328 }
329 auto ncount = std::count(favorites.begin(), favorites.end(), part_current);
330 assert(ncount < 2);
331 fav_toggled_conn.block();
332 fav_button->set_active(ncount > 0);
333 fav_toggled_conn.unblock();
334
335 place_part_button->set_sensitive(part_current);
336 assign_part_button->set_sensitive(part_current && can_assign);
337 fav_button->set_sensitive(part_current);
338 if (part_current) {
339 auto part = pool.get_part(part_current);
340 preview->load(part);
341 }
342 else {
343 preview->load(nullptr);
344 }
345 update_out_of_date_info_bar();
346 }
347
set_pool_cache_status(const PoolCacheStatus & st)348 void PartBrowserWindow::set_pool_cache_status(const PoolCacheStatus &st)
349 {
350 pool_cache_status = &st;
351 update_out_of_date_info_bar();
352 }
353
update_out_of_date_info_bar()354 void PartBrowserWindow::update_out_of_date_info_bar()
355 {
356 info_bar_hide(out_of_date_info_bar);
357 items_out_of_date.clear();
358 if (!part_current || !pool_cache_status) {
359 return;
360 }
361 SQLite::Query q(pool.db,
362 "WITH RECURSIVE deps(typex, uuidx) AS "
363 "( SELECT 'part', ? UNION "
364 "SELECT dep_type, dep_uuid FROM dependencies, deps "
365 "WHERE dependencies.type = deps.typex AND dependencies.uuid = deps.uuidx) "
366 "SELECT * FROM deps UNION SELECT 'symbol', symbols.uuid FROM symbols "
367 "INNER JOIN deps ON (symbols.unit = uuidx AND typex = 'unit')");
368 q.bind(1, part_current);
369 while (q.step()) {
370 auto type = q.get<ObjectType>(0);
371 UUID uu(q.get<std::string>(1));
372
373 auto r = std::find_if(pool_cache_status->items.begin(), pool_cache_status->items.end(),
374 [type, uu](const PoolCacheStatus::Item &it) { return it.type == type && it.uuid == uu; });
375 if (r != pool_cache_status->items.end()) {
376 if (r->state == PoolCacheStatus::Item::State::OUT_OF_DATE) {
377 items_out_of_date.emplace(type, uu);
378 }
379 }
380 }
381 if (items_out_of_date.size()) {
382 info_bar_show(out_of_date_info_bar);
383 }
384 }
385
set_can_assign(bool v)386 void PartBrowserWindow::set_can_assign(bool v)
387 {
388 can_assign = v;
389 assign_part_button->set_sensitive(part_current && can_assign);
390 }
391
add_search(const UUID & part)392 PoolBrowserPart *PartBrowserWindow::add_search(const UUID &part)
393 {
394 auto ch = Gtk::manage(new PoolBrowserPart(pool, UUID(), "part_browser"));
395 ch->set_include_base_parts(false);
396 if (auto prv = StockInfoProvider::create(pool.get_base_path())) {
397 ch->add_stock_info_provider(std::move(prv));
398 }
399 ch->get_style_context()->add_class("background");
400 auto tab_label = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
401 auto la = Gtk::manage(new Gtk::Label("MPN Search"));
402 la->set_xalign(1);
403 auto close_button = Gtk::manage(new Gtk::Button());
404 close_button->set_relief(Gtk::RELIEF_NONE);
405 close_button->set_image_from_icon_name("window-close-symbolic");
406 close_button->signal_clicked().connect([ch] { delete ch; });
407 tab_label->pack_start(*close_button, false, false, 0);
408 tab_label->pack_start(*la, true, true, 0);
409 ch->show_all();
410 tab_label->show_all();
411 auto index = notebook->append_page(*ch, *tab_label);
412 notebook->set_current_page(index);
413
414 search_views.insert(ch);
415 ch->signal_selected().connect(sigc::mem_fun(*this, &PartBrowserWindow::update_part_current));
416 ch->signal_activated().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_place_part));
417 if (part)
418 ch->go_to(part);
419 ch->focus_search();
420 return ch;
421 }
422
add_search_parametric(const std::string & table_name)423 PoolBrowserParametric *PartBrowserWindow::add_search_parametric(const std::string &table_name)
424 {
425 auto ch = Gtk::manage(new PoolBrowserParametric(pool, pool_parametric, table_name, "part_browser"));
426 if (auto prv = StockInfoProvider::create(pool.get_base_path())) {
427 ch->add_stock_info_provider(std::move(prv));
428 }
429 ch->add_copy_name_context_menu_item();
430 ch->get_style_context()->add_class("background");
431 auto tab_label = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
432 auto la = Gtk::manage(new Gtk::Label(pool_parametric.get_tables().at(table_name).display_name));
433 la->set_xalign(1);
434 auto close_button = Gtk::manage(new Gtk::Button());
435 close_button->set_relief(Gtk::RELIEF_NONE);
436 close_button->set_image_from_icon_name("window-close-symbolic");
437 close_button->signal_clicked().connect([ch] { delete ch; });
438 tab_label->pack_start(*close_button, false, false, 0);
439 tab_label->pack_start(*la, true, true, 0);
440 ch->show_all();
441 tab_label->show_all();
442 auto index = notebook->append_page(*ch, *tab_label);
443 notebook->set_current_page(index);
444
445 search_views.insert(ch);
446 ch->signal_selected().connect(sigc::mem_fun(*this, &PartBrowserWindow::update_part_current));
447 ch->signal_activated().connect(sigc::mem_fun(*this, &PartBrowserWindow::handle_place_part));
448
449 return ch;
450 }
451
focus_search()452 void PartBrowserWindow::focus_search()
453 {
454 auto page = notebook->get_nth_page(notebook->get_current_page());
455 if (auto br = dynamic_cast<PoolBrowserPart *>(page)) {
456 br->focus_search();
457 }
458 }
459
create(Gtk::Window * p,const std::string & pool_path,std::deque<UUID> & favs)460 PartBrowserWindow *PartBrowserWindow::create(Gtk::Window *p, const std::string &pool_path, std::deque<UUID> &favs)
461 {
462 PartBrowserWindow *w;
463 Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
464 x->add_from_resource(
465 "/org/horizon-eda/horizon/pool-prj-mgr/prj-mgr/part_browser/"
466 "part_browser.ui");
467 x->get_widget_derived("window", w, pool_path, favs);
468
469 return w;
470 }
471 } // namespace horizon
472