1 #include "selection_filter_dialog.hpp"
2 #include "util/gtk_util.hpp"
3 #include "common/common.hpp"
4 #include "canvas/selection_filter.hpp"
5 #include "imp/imp.hpp"
6 #include "board/board_layers.hpp"
7 
8 namespace horizon {
9 
10 
11 static const auto sub_margin = 35;
12 
13 
SelectionFilterDialog(Gtk::Window * parent,SelectionFilter & sf,ImpBase & aimp)14 SelectionFilterDialog::SelectionFilterDialog(Gtk::Window *parent, SelectionFilter &sf, ImpBase &aimp)
15     : Gtk::Window(), selection_filter(sf), imp(aimp)
16 {
17     set_default_size(220, 300);
18     set_type_hint(Gdk::WINDOW_TYPE_HINT_DIALOG);
19     set_transient_for(*parent);
20     auto hb = Gtk::manage(new Gtk::HeaderBar());
21     hb->set_show_close_button(true);
22     set_titlebar(*hb);
23     hb->show();
24     set_title("Selection filter");
25     install_esc_to_close(*this);
26 
27     reset_button = Gtk::manage(new Gtk::Button());
28     reset_button->set_image_from_icon_name("object-select-symbolic", Gtk::ICON_SIZE_BUTTON);
29     reset_button->set_tooltip_text("Select all");
30     reset_button->show_all();
31     reset_button->signal_clicked().connect([this] {
32         set_all(true);
33         update();
34     });
35     hb->pack_start(*reset_button);
36     reset_button->show();
37 
38     listbox = Gtk::manage(new Gtk::ListBox());
39     listbox->set_selection_mode(Gtk::SELECTION_NONE);
40     listbox->set_header_func(sigc::ptr_fun(&header_func_separator));
41     auto sg = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
42     for (const auto &it : imp.get_selection_filter_info()) {
43         auto ot = it.first;
44 
45         {
46             auto cb = Gtk::manage(new Gtk::CheckButton(object_descriptions.at(it.first).name_pl));
47             cb->set_active(true);
48             if (it.second.layers.size() == 0) {
49                 cb->signal_toggled().connect([this] {
50                     update_filter();
51                     update();
52                 });
53             }
54             else {
55                 cb->signal_toggled().connect([this, ot, cb] {
56                     if (checkbuttons[ot].blocked)
57                         return;
58                     checkbuttons[ot].blocked = true;
59                     for (auto &cbl : checkbuttons[ot].layer_buttons) {
60                         cbl.second->set_active(cb->get_active());
61                     }
62                     if (checkbuttons[ot].other_layer_checkbutton) {
63                         checkbuttons[ot].other_layer_checkbutton->set_active(cb->get_active());
64                     }
65                     checkbuttons[ot].blocked = false;
66                     update();
67                 });
68             }
69             connect_doubleclick(cb);
70 
71             checkbuttons[ot].checkbutton = cb;
72             checkbuttons[ot].work_layer_only_enabled =
73                     it.second.flags == ImpBase::SelectionFilterInfo::Flag::WORK_LAYER_ONLY_ENABLED;
74             auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 3));
75             box->property_margin() = 3;
76             if (it.second.layers.size() != 0) {
77                 auto expand_button = Gtk::manage(new Gtk::ToggleButton);
78                 checkbuttons.at(ot).expand_button = expand_button;
79                 expand_button->set_image_from_icon_name("pan-end-symbolic", Gtk::ICON_SIZE_BUTTON);
80                 expand_button->get_style_context()->add_class("imp-selection-filter-tiny-button");
81                 expand_button->set_relief(Gtk::RELIEF_NONE);
82                 sg->add_widget(*expand_button);
83                 box->pack_start(*expand_button, false, false, 0);
84                 expand_button->signal_toggled().connect([this, expand_button, ot] {
85                     auto show = expand_button->get_active();
86                     checkbuttons.at(ot).expanded = show;
87                     if (show) {
88                         expand_button->set_image_from_icon_name("pan-down-symbolic", Gtk::ICON_SIZE_BUTTON);
89                     }
90                     else {
91                         expand_button->set_image_from_icon_name("pan-end-symbolic", Gtk::ICON_SIZE_BUTTON);
92                     }
93                     for (auto &it_cb : checkbuttons.at(ot).layer_buttons) {
94                         it_cb.second->get_parent()->set_visible(show);
95                     }
96                     if (checkbuttons.at(ot).other_layer_checkbutton) {
97                         checkbuttons.at(ot).other_layer_checkbutton->get_parent()->set_visible(show);
98                     }
99                 });
100             }
101             else {
102                 auto placeholder = Gtk::manage(new Gtk::Label);
103                 sg->add_widget(*placeholder);
104                 box->pack_start(*placeholder, false, false, 0);
105             }
106             box->pack_start(*cb, true, true, 0);
107             box->show_all();
108             listbox->append(*box);
109         }
110         for (auto layer : it.second.layers) {
111             add_layer_button(ot, layer, -1);
112         }
113         if (it.second.flags == ImpBase::SelectionFilterInfo::Flag::HAS_OTHERS) {
114             auto cbl = Gtk::manage(new Gtk::CheckButton("Others"));
115             cbl->property_margin() = 3;
116             cbl->set_margin_start(sub_margin);
117             cbl->set_active(true);
118             checkbuttons[ot].other_layer_checkbutton = cbl;
119             listbox->append(*cbl);
120             cbl->show();
121             cbl->get_parent()->set_visible(false);
122             connect_doubleclick(cbl);
123             cbl->signal_toggled().connect([this, ot] {
124                 update_filter();
125                 if (!checkbuttons[ot].blocked)
126                     update();
127             });
128         }
129     }
130     listbox->set_no_show_all(true);
131     listbox->show();
132     auto sc = Gtk::manage(new Gtk::ScrolledWindow());
133     sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
134     sc->add(*listbox);
135 
136     auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
137 
138     if (imp.is_layered()) {
139         work_layer_only_cb = Gtk::manage(new Gtk::CheckButton("Work layer only"));
140         work_layer_only_cb->property_margin() = 3;
141         work_layer_only_cb->signal_toggled().connect(
142                 sigc::mem_fun(*this, &SelectionFilterDialog::update_work_layer_only));
143         box->pack_start(*work_layer_only_cb, false, false, 0);
144         {
145             auto sep = Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL));
146             box->pack_start(*sep, false, false, 0);
147         }
148     }
149 
150     box->pack_start(*sc, true, true, 0);
151 
152     {
153         auto sep = Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL));
154         box->pack_start(*sep, false, false, 0);
155     }
156 
157     {
158         auto la = Gtk::manage(new Gtk::Label("Double click to select just one."));
159         make_label_small(la);
160         la->property_margin() = 3;
161         la->get_style_context()->add_class("dim-label");
162         box->pack_start(*la, false, false, 0);
163     }
164     add(*box);
165     box->show_all();
166     update();
167     update_filter();
168 }
169 
update_filter()170 void SelectionFilterDialog::update_filter()
171 {
172     for (const auto &it : checkbuttons) {
173         if (it.second.layer_buttons.size() == 0) {
174             if (work_layer_only && !it.second.work_layer_only_enabled)
175                 selection_filter.object_filter[it.first].other_layers = false;
176             else
177                 selection_filter.object_filter[it.first].other_layers = it.second.checkbutton->get_active();
178         }
179         else {
180             if (work_layer_only) {
181                 selection_filter.object_filter[it.first].other_layers = false;
182                 if (it.second.checkbutton->get_active())
183                     selection_filter.object_filter[it.first].layers = {{work_layer, true}};
184                 else
185                     selection_filter.object_filter[it.first].layers.clear();
186             }
187             else {
188                 if (it.second.other_layer_checkbutton)
189                     selection_filter.object_filter[it.first].other_layers =
190                             it.second.other_layer_checkbutton->get_active();
191                 for (auto &it_la : it.second.layer_buttons) {
192                     selection_filter.object_filter[it.first].layers[it_la.first] = it_la.second->get_active();
193                 }
194             }
195         }
196     }
197 }
198 
set_work_layer(int layer)199 void SelectionFilterDialog::set_work_layer(int layer)
200 {
201     work_layer = layer;
202     update_work_layer_only();
203 }
204 
update_work_layer_only()205 void SelectionFilterDialog::update_work_layer_only()
206 {
207     if (!work_layer_only_cb)
208         return;
209     work_layer_only = work_layer_only_cb->get_active();
210     if (work_layer_only && !work_layer_only_before) { // got activated, save
211         saved.clear();
212         for (auto &it : checkbuttons) {
213             saved[it.first];
214             if (it.second.layer_buttons.size() == 0 && !it.second.work_layer_only_enabled) {
215                 if (it.second.checkbutton->get_active())
216                     saved.at(it.first).insert(0);
217                 it.second.checkbutton->set_active(false);
218                 it.second.checkbutton->set_sensitive(false);
219             }
220             else if (it.second.expand_button) {
221                 if (it.second.expand_button->get_active())
222                     saved.at(it.first).insert(10001);
223                 it.second.expand_button->set_active(false);
224                 it.second.expand_button->set_sensitive(false);
225             }
226         }
227     }
228 
229     if (!work_layer_only && work_layer_only_before) { // got deactivated, restore
230         for (auto &it : checkbuttons) {
231             if (saved.count(it.first)) {
232                 const auto &sav = saved.at(it.first);
233                 it.second.checkbutton->set_sensitive(true);
234                 if (it.second.layer_buttons.size() == 0 && !it.second.work_layer_only_enabled) {
235                     it.second.checkbutton->set_active(sav.count(0));
236                 }
237                 else if (it.second.expand_button) {
238                     it.second.expand_button->set_active(sav.count(10001));
239                     it.second.expand_button->set_sensitive(true);
240                 }
241             }
242         }
243     }
244     work_layer_only_before = work_layer_only;
245     update_filter();
246 }
247 
get_all_active()248 bool SelectionFilterDialog::Type::get_all_active()
249 {
250     if (layer_buttons.size() == 0) {
251         return checkbutton->get_active();
252     }
253     bool all_on =
254             std::all_of(layer_buttons.begin(), layer_buttons.end(), [](auto &x) { return x.second->get_active(); });
255     if (other_layer_checkbutton) {
256         all_on = all_on && other_layer_checkbutton->get_active();
257     }
258     return all_on;
259 }
260 
update()261 void SelectionFilterDialog::Type::update()
262 {
263     if (layer_buttons.size() == 0)
264         return;
265     blocked = true;
266     bool all_on =
267             std::all_of(layer_buttons.begin(), layer_buttons.end(), [](auto &x) { return x.second->get_active(); });
268     bool all_off =
269             std::all_of(layer_buttons.begin(), layer_buttons.end(), [](auto &x) { return !x.second->get_active(); });
270     if (other_layer_checkbutton) {
271         all_on = all_on && other_layer_checkbutton->get_active();
272         all_off = all_off && !other_layer_checkbutton->get_active();
273     }
274     if (all_on) {
275         checkbutton->set_inconsistent(false);
276         checkbutton->set_active(true);
277     }
278     else if (all_off) {
279         checkbutton->set_inconsistent(false);
280         checkbutton->set_active(false);
281     }
282     else {
283         checkbutton->set_active(true);
284         checkbutton->set_inconsistent(true);
285     }
286     blocked = false;
287 }
288 
289 
update()290 void SelectionFilterDialog::update()
291 {
292     if (!work_layer_only) {
293         reset_button->set_sensitive(!std::all_of(checkbuttons.begin(), checkbuttons.end(),
294                                                  [](auto x) { return x.second.get_all_active(); }));
295     }
296     else {
297         reset_button->set_sensitive(false);
298         for (const auto &it : checkbuttons) {
299             if (it.second.layer_buttons.size()) {
300                 if (!it.second.checkbutton->get_active()) {
301                     reset_button->set_sensitive(true);
302                     break;
303                 }
304             }
305         }
306     }
307     for (auto &it : checkbuttons) {
308         if (work_layer_only) {
309             it.second.checkbutton->set_inconsistent(false);
310         }
311         else {
312             it.second.update();
313         }
314     }
315     s_signal_changed.emit();
316 }
317 
set_all(bool state)318 void SelectionFilterDialog::set_all(bool state)
319 {
320     for (auto &it : checkbuttons) {
321         for (auto cb : it.second.layer_buttons) {
322             cb.second->set_active(state);
323         }
324         if (it.second.other_layer_checkbutton)
325             it.second.other_layer_checkbutton->set_active(state);
326         if (it.second.checkbutton->get_sensitive())
327             it.second.checkbutton->set_active(state);
328     }
329 }
330 
connect_doubleclick(Gtk::CheckButton * cb)331 void SelectionFilterDialog::connect_doubleclick(Gtk::CheckButton *cb)
332 {
333     cb->signal_button_press_event().connect(
334             [this, cb](GdkEventButton *ev) {
335                 if (ev->type == GDK_2BUTTON_PRESS) {
336                     set_all(false);
337                     cb->set_active(true);
338                 }
339                 else if (ev->type == GDK_BUTTON_PRESS) {
340                     cb->set_active(!cb->get_active());
341                 }
342                 update();
343                 return true;
344             },
345             false);
346 }
347 
add_layer_button(ObjectType type,int layer,int index,bool active)348 Gtk::CheckButton *SelectionFilterDialog::add_layer_button(ObjectType type, int layer, int index, bool active)
349 {
350     auto cbl = Gtk::manage(new Gtk::CheckButton(BoardLayers::get_layer_name(layer)));
351     cbl->property_margin() = 3;
352     cbl->set_margin_start(sub_margin);
353     checkbuttons[type].layer_buttons[layer] = cbl;
354     listbox->insert(*cbl, index);
355     cbl->show();
356     cbl->get_parent()->set_visible(false);
357     cbl->set_active(active);
358     connect_doubleclick(cbl);
359     cbl->signal_toggled().connect([this, type] {
360         update_filter();
361         if (!checkbuttons[type].blocked)
362             update();
363     });
364     update_filter();
365     return cbl;
366 }
367 
update_layers()368 void SelectionFilterDialog::update_layers()
369 {
370     auto info = imp.get_selection_filter_info();
371     for (auto &it_type : checkbuttons) {
372         std::set<int> layers_current;
373         std::set<int> layers_new;
374         std::transform(it_type.second.layer_buttons.begin(), it_type.second.layer_buttons.end(),
375                        std::inserter(layers_current, layers_current.begin()), [](auto &x) { return x.first; });
376         std::transform(info.at(it_type.first).layers.begin(), info.at(it_type.first).layers.end(),
377                        std::inserter(layers_new, layers_new.begin()), [](auto &x) { return x; });
378         if (layers_new != layers_current) {
379             std::map<int, bool> state;
380             for (auto &it : it_type.second.layer_buttons) {
381                 state[it.first] = it.second->get_active();
382                 delete it.second->get_parent();
383             }
384             it_type.second.layer_buttons.clear();
385             auto inf = info.at(it_type.first);
386             int index = dynamic_cast<Gtk::ListBoxRow *>(it_type.second.checkbutton->get_ancestor(GTK_TYPE_LIST_BOX_ROW))
387                                 ->get_index();
388             for (auto layer : inf.layers) {
389                 index++;
390                 bool active = true;
391                 if (state.count(layer))
392                     active = state.at(layer);
393                 auto cb = add_layer_button(it_type.first, layer, index, active);
394                 cb->get_parent()->set_visible(it_type.second.expanded);
395             }
396         }
397     }
398 }
399 
get_filtered()400 bool SelectionFilterDialog::get_filtered()
401 {
402     return work_layer_only
403            || !std::all_of(checkbuttons.begin(), checkbuttons.end(), [](auto x) { return x.second.get_all_active(); });
404 }
405 
force_work_layer_only(bool force)406 void SelectionFilterDialog::force_work_layer_only(bool force)
407 {
408     if (force) {
409         if (work_layer_only_cb->get_sensitive())
410             work_layer_only_before_force = work_layer_only;
411         work_layer_only_cb->set_active(true);
412     }
413     else {
414         if (!work_layer_only_cb->get_sensitive())
415             work_layer_only_cb->set_active(work_layer_only_before_force);
416     }
417     work_layer_only_cb->set_sensitive(!force);
418 }
419 
420 } // namespace horizon
421