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