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