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