1 #include "airwire_filter_window.hpp"
2 #include "board/board.hpp"
3 #include "util/util.hpp"
4 #include "util/gtk_util.hpp"
5 #include "util/str_util.hpp"
6 #include "widgets/cell_renderer_color_box.hpp"
7 #include "nlohmann/json.hpp"
8 
9 namespace horizon {
create(Gtk::Window * p,const class Board & b)10 AirwireFilterWindow *AirwireFilterWindow::create(Gtk::Window *p, const class Board &b)
11 {
12     AirwireFilterWindow *w;
13     Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
14     x->add_from_resource("/org/horizon-eda/horizon/imp/airwire_filter.ui");
15     x->get_widget_derived("window", w, b);
16     w->set_transient_for(*p);
17     return w;
18 }
19 
AirwireFilterWindow(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,const class Board & bd)20 AirwireFilterWindow::AirwireFilterWindow(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x,
21                                          const class Board &bd)
22     : Gtk::Window(cobject), brd(bd), block(*bd.block)
23 {
24     install_esc_to_close(*this);
25 
26     GET_WIDGET(treeview);
27     GET_WIDGET(all_on_button);
28     GET_WIDGET(all_off_button);
29     GET_WIDGET(search_button);
30     GET_WIDGET(search_revealer);
31     GET_WIDGET(airwires_button);
32     GET_WIDGET(airwires_revealer);
33     GET_WIDGET(netclass_combo);
34     GET_WIDGET(search_entry);
35     GET_WIDGET(airwires_only_cb);
36 
37     airwires_button->signal_toggled().connect(
38             [this] { airwires_revealer->set_reveal_child(airwires_button->get_active()); });
39 
40     search_button->signal_toggled().connect([this] {
41         search_revealer->set_reveal_child(search_button->get_active());
42         if (search_button->get_active())
43             search_entry->grab_focus();
44     });
45 
46     store = Gtk::ListStore::create(list_columns);
47     store_filtered = Gtk::TreeModelFilter::create(store);
48     store_filtered->set_visible_func([this](const Gtk::TreeModel::const_iterator &it) -> bool {
49         const Gtk::TreeModel::Row row = *it;
50         if (airwires_only_cb->get_active()) {
51             if (row[list_columns.n_airwires] == 0)
52                 return false;
53         }
54         if (netclass_filter) {
55             if (row[list_columns.net_class] != netclass_filter)
56                 return false;
57         }
58         if (search_spec.has_value()) {
59             return search_spec->match(row.get_value(list_columns.net_name).casefold());
60         }
61         return true;
62     });
63     store_sorted = Gtk::TreeModelSort::create(store_filtered);
64 
65     treeview->set_model(store_sorted);
66     {
67         auto tvc = Gtk::manage(new Gtk::TreeViewColumn("Net"));
68 
69         auto cr_color = Gtk::manage(new CellRendererColorBox);
70         tvc->pack_start(*cr_color, false);
71         tvc->add_attribute(cr_color->property_color(), list_columns.color);
72 
73         auto cr = Gtk::manage(new Gtk::CellRendererText());
74         tvc->pack_start(*cr, true);
75         tvc->add_attribute(cr->property_text(), list_columns.net_name);
76         tvc->set_sort_column(list_columns.net_name);
77         treeview->append_column(*tvc);
78     }
79     {
80         auto tvc = Gtk::manage(new Gtk::TreeViewColumn("Net class"));
81         auto cr = Gtk::manage(new Gtk::CellRendererText());
82         tvc->pack_start(*cr, true);
83         tvc->add_attribute(cr->property_text(), list_columns.net_class_name);
84         tvc->set_sort_column(list_columns.net_class_name);
85         treeview->append_column(*tvc);
86     }
87     {
88         auto tvc = Gtk::manage(new Gtk::TreeViewColumn("Airwires"));
89         auto cr_toggle = Gtk::manage(new Gtk::CellRendererToggle());
90         cr_toggle->property_xalign() = 1;
91         tvc->pack_start(*cr_toggle, false);
92         tvc->add_attribute(cr_toggle->property_active(), list_columns.airwires_visible);
93         cr_toggle->signal_toggled().connect([this](const Glib::ustring &path) {
94             auto it = store->get_iter(store_filtered->convert_path_to_child_path(
95                     store_sorted->convert_path_to_child_path(Gtk::TreeModel::Path(path))));
96             if (it) {
97                 Gtk::TreeModel::Row row = *it;
98                 const bool v = !row[list_columns.airwires_visible];
99                 airwires_visible[row[list_columns.net]] = v;
100                 row[list_columns.airwires_visible] = v;
101                 s_signal_changed.emit();
102             }
103         });
104 
105 
106         auto cr = Gtk::manage(new Gtk::CellRendererText());
107         tvc->pack_start(*cr, true);
108         tvc->add_attribute(cr->property_text(), list_columns.n_airwires);
109         tvc->set_sort_column(list_columns.n_airwires);
110 
111         treeview->append_column(*tvc);
112     }
113     store_sorted->set_sort_column(list_columns.net_name, Gtk::SORT_ASCENDING);
114     store_sorted->set_sort_column(list_columns.n_airwires, Gtk::SORT_ASCENDING);
115     store_sorted->set_sort_func(list_columns.net_name,
116                                 [this](const Gtk::TreeModel::iterator &ia, const Gtk::TreeModel::iterator &ib) {
117                                     Gtk::TreeModel::Row ra = *ia;
118                                     Gtk::TreeModel::Row rb = *ib;
119                                     Glib::ustring a = ra[list_columns.net_name];
120                                     Glib::ustring b = rb[list_columns.net_name];
121                                     return strcmp_natural(a, b);
122                                 });
123 
124     all_on_button->signal_clicked().connect([this] { set_all(true); });
125     all_off_button->signal_clicked().connect([this] { set_all(false); });
126 
127 
128     append_context_menu_item("Check", MenuOP::CHECK);
129     append_context_menu_item("Uncheck", MenuOP::UNCHECK);
130     append_context_menu_item("Toggle", MenuOP::TOGGLE);
131     append_context_menu_item("Set color", MenuOP::SET_COLOR);
132     append_context_menu_item("Clear color", MenuOP::CLEAR_COLOR);
133 
134     treeview->signal_button_press_event().connect(
135             [this](GdkEventButton *ev) {
136                 Gtk::TreeModel::Path path;
137                 if (gdk_event_triggers_context_menu((GdkEvent *)ev)
138                     && treeview->get_selection()->count_selected_rows()) {
139 #if GTK_CHECK_VERSION(3, 22, 0)
140                     context_menu.popup_at_pointer((GdkEvent *)ev);
141 #else
142                     context_menu.popup(ev->button, gtk_get_current_event_time());
143 #endif
144                     return true;
145                 }
146                 return false;
147             },
148             false);
149 
150     treeview->get_selection()->signal_changed().connect([this] {
151         std::set<UUID> nets;
152         auto rows = treeview->get_selection()->get_selected_rows();
153         for (const auto &path : rows) {
154             Gtk::TreeModel::Row row = *store_sorted->get_iter(path);
155             nets.emplace(row[list_columns.net]);
156         }
157         s_signal_selection_changed.emit(nets);
158     });
159 
160     netclass_combo->signal_changed().connect([this] {
161         try {
162             netclass_filter = UUID(netclass_combo->get_active_id());
163         }
164         catch (...) {
165             netclass_filter = UUID();
166         }
167         store_filtered->refilter();
168     });
169     search_entry->signal_search_changed().connect([this] {
170         search_spec.reset();
171         auto utxt = search_entry->get_text();
172         std::string txt = utxt.casefold();
173         trim(txt);
174         if (txt.size())
175             search_spec.emplace("*" + txt + "*");
176         store_filtered->refilter();
177     });
178     airwires_only_cb->signal_toggled().connect([this] { store_filtered->refilter(); });
179     s_signal_changed.connect([this] {
180         bool all_on = true;
181         bool all_off = true;
182         for (const auto &it : store->children()) {
183             Gtk::TreeModel::Row row = *it;
184             const bool v = row[list_columns.airwires_visible];
185             if (v)
186                 all_off = false;
187             else
188                 all_on = false;
189         }
190         all_on_button->set_sensitive(!all_on);
191         all_off_button->set_sensitive(!all_off);
192     });
193 }
194 
airwire_is_visible(const UUID & net) const195 bool AirwireFilterWindow::airwire_is_visible(const UUID &net) const
196 {
197     if (airwires_visible.count(net))
198         return airwires_visible.at(net);
199     else
200         return true;
201 }
202 
set_all(bool v)203 void AirwireFilterWindow::set_all(bool v)
204 {
205     for (auto &it : store->children()) {
206         Gtk::TreeModel::Row row = *it;
207         row[list_columns.airwires_visible] = v;
208         airwires_visible[row[list_columns.net]] = v;
209     }
210     s_signal_changed.emit();
211 }
212 
set_only(const std::set<UUID> & nets)213 void AirwireFilterWindow::set_only(const std::set<UUID> &nets)
214 {
215     for (auto &it : store->children()) {
216         Gtk::TreeModel::Row row = *it;
217         const bool v = nets.count(row[list_columns.net]);
218         row[list_columns.airwires_visible] = v;
219         airwires_visible[row[list_columns.net]] = v;
220     }
221     s_signal_changed.emit();
222 }
223 
rgba_from_colori(ColorI c)224 static Gdk::RGBA rgba_from_colori(ColorI c)
225 {
226     Gdk::RGBA rg;
227     rg.set_red(c.r / 255.);
228     rg.set_green(c.g / 255.);
229     rg.set_blue(c.b / 255.);
230     rg.set_alpha(1);
231     return rg;
232 }
233 
append_context_menu_item(const std::string & name,MenuOP op)234 void AirwireFilterWindow::append_context_menu_item(const std::string &name, MenuOP op)
235 {
236     auto it = Gtk::manage(new Gtk::MenuItem(name));
237     it->signal_activate().connect([this, op] {
238         auto rows = treeview->get_selection()->get_selected_rows();
239         ColorI color;
240         Gdk::RGBA rgba;
241         if (op == MenuOP::SET_COLOR) {
242             auto cdia = GTK_COLOR_CHOOSER_DIALOG(gtk_color_chooser_dialog_new("Pick net color", gobj()));
243             auto dia = Glib::wrap(cdia, false);
244             dia->property_show_editor() = true;
245             dia->set_use_alpha(false);
246             for (const auto &path : rows) {
247                 Gtk::TreeModel::Row row = *store->get_iter(
248                         store_filtered->convert_path_to_child_path(store_sorted->convert_path_to_child_path(path)));
249                 UUID net = row[list_columns.net];
250                 if (net_colors.count(net)) {
251                     const auto c = net_colors.at(net);
252                     dia->set_rgba(rgba_from_colori(c));
253                     break;
254                 }
255             }
256 
257 
258             if (dia->run() == Gtk::RESPONSE_OK) {
259                 rgba = dia->get_rgba();
260                 color.r = rgba.get_red() * 255;
261                 color.g = rgba.get_green() * 255;
262                 color.b = rgba.get_blue() * 255;
263                 gtk_widget_destroy(GTK_WIDGET(cdia));
264             }
265             else {
266                 gtk_widget_destroy(GTK_WIDGET(cdia));
267                 return;
268             }
269         }
270         for (const auto &path : rows) {
271             Gtk::TreeModel::Row row = *store->get_iter(
272                     store_filtered->convert_path_to_child_path(store_sorted->convert_path_to_child_path(path)));
273             switch (op) {
274             case MenuOP::CHECK:
275                 row[list_columns.airwires_visible] = true;
276                 airwires_visible[row[list_columns.net]] = true;
277                 break;
278             case MenuOP::UNCHECK:
279                 row[list_columns.airwires_visible] = false;
280                 airwires_visible[row[list_columns.net]] = false;
281                 break;
282             case MenuOP::TOGGLE:
283                 row[list_columns.airwires_visible] = !row[list_columns.airwires_visible];
284                 airwires_visible[row[list_columns.net]] = row[list_columns.airwires_visible];
285                 break;
286             case MenuOP::CLEAR_COLOR: {
287                 auto v = row.get_value(list_columns.color);
288                 if (v.gobj()) {
289                     v.set_alpha(0);
290                     row.set_value(list_columns.color, v);
291                 }
292                 net_colors.erase(row[list_columns.net]);
293             } break;
294             case MenuOP::SET_COLOR:
295                 row[list_columns.color] = rgba;
296                 net_colors[row[list_columns.net]] = color;
297                 break;
298             }
299         }
300         s_signal_changed.emit();
301     });
302     context_menu.append(*it);
303     it->show();
304 }
305 
update_nets()306 void AirwireFilterWindow::update_nets()
307 {
308     store->clear();
309     for (const auto &[net_uuid, net] : block.nets) {
310         Gtk::TreeModel::Row row = *store->append();
311         row[list_columns.net] = net_uuid;
312         row[list_columns.net_class] = net.net_class->uuid;
313         row[list_columns.net_name] = block.get_net_name(net_uuid);
314         row[list_columns.net_class_name] = net.net_class->name;
315         row[list_columns.airwires_visible] = airwire_is_visible(net_uuid);
316         if (brd.airwires.count(net_uuid)) {
317             row[list_columns.n_airwires] = brd.airwires.at(net_uuid).size();
318         }
319         else {
320             row[list_columns.n_airwires] = 0;
321         }
322     }
323     auto nc_current = netclass_combo->get_active_id();
324     netclass_combo->remove_all();
325     netclass_combo->append((std::string)UUID(), "All");
326     for (const auto &[nc_uuid, nc] : block.net_classes) {
327         netclass_combo->append((std::string)nc_uuid, nc.name);
328     }
329     try {
330         auto uu = UUID(nc_current);
331         if (block.net_classes.count(uu)) {
332             netclass_combo->set_active_id(nc_current);
333         }
334         else {
335             netclass_combo->set_active_id((std::string)UUID());
336         }
337     }
338     catch (...) {
339         netclass_combo->set_active_id((std::string)UUID());
340     }
341     s_signal_changed.emit();
342 }
343 
get_filtered() const344 bool AirwireFilterWindow::get_filtered() const
345 {
346     return std::any_of(airwires_visible.begin(), airwires_visible.end(),
347                        [](const auto &x) { return x.second == false; });
348 }
349 
update_from_board()350 void AirwireFilterWindow::update_from_board()
351 {
352     for (auto &it : store->children()) {
353         Gtk::TreeModel::Row row = *it;
354         if (brd.airwires.count(row[list_columns.net])) {
355             row[list_columns.n_airwires] = brd.airwires.at(row[list_columns.net]).size();
356         }
357     }
358     s_signal_changed.emit();
359 }
360 
load_from_json(const json & j)361 void AirwireFilterWindow::load_from_json(const json &j)
362 {
363     for (const auto &[key, value] : j.at("airwires_visible").items()) {
364         const UUID net = key;
365         if (block.nets.count(net))
366             airwires_visible[net] = value;
367     }
368     for (const auto &[key, value] : j.at("net_colors").items()) {
369         const UUID net = key;
370         if (block.nets.count(net))
371             net_colors[net] = colori_from_json(value);
372     }
373     for (auto &it : store->children()) {
374         Gtk::TreeModel::Row row = *it;
375         const UUID net = row[list_columns.net];
376         if (airwires_visible.count(net)) {
377             row[list_columns.airwires_visible] = airwires_visible.at(net);
378         }
379         if (net_colors.count(net)) {
380             row[list_columns.color] = rgba_from_colori(net_colors.at(net));
381         }
382     }
383     s_signal_changed.emit();
384 }
385 
serialize()386 json AirwireFilterWindow::serialize()
387 {
388     json j;
389     {
390         json o;
391         for (const auto &[net, v] : airwires_visible) {
392             o[(std::string)net] = v;
393         }
394         j["airwires_visible"] = o;
395     }
396     {
397         json o;
398         for (const auto &[net, color] : net_colors) {
399             o[(std::string)net] = colori_to_json(color);
400         }
401         j["net_colors"] = o;
402     }
403 
404     return j;
405 }
406 
407 }; // namespace horizon
408