1 #include "3d_view.hpp"
2 #include "canvas3d/canvas3d.hpp"
3 #include "import_step/import.hpp"
4 #include "util/gtk_util.hpp"
5 #include "board/board.hpp"
6 #include "pool/part.hpp"
7 #include "util/str_util.hpp"
8 #include "axes_lollipop.hpp"
9 #include "imp/action_catalog.hpp"
10 #include "imp/actions.hpp"
11 #include "preferences/preferences.hpp"
12 
13 namespace horizon {
14 
create(const class Board & b,class IPool & p,Mode m,Canvas3D * ca_custom)15 View3DWindow *View3DWindow::create(const class Board &b, class IPool &p, Mode m, Canvas3D *ca_custom)
16 {
17     View3DWindow *w;
18     Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
19     x->add_from_resource("/org/horizon-eda/horizon/imp/3d/3d_view.ui");
20     x->get_widget_derived("window", w, b, p, m, ca_custom);
21 
22     return w;
23 }
24 
bind_color_button(Gtk::ColorButton * color_button,FnSetColor fn_set,std::function<void (void)> extra_fn)25 void View3DWindow::bind_color_button(Gtk::ColorButton *color_button, FnSetColor fn_set,
26                                      std::function<void(void)> extra_fn)
27 {
28     color_button->property_color().signal_changed().connect([this, color_button, fn_set, extra_fn] {
29         auto co = color_button->get_color();
30         Color color;
31         color.r = co.get_red_p();
32         color.g = co.get_green_p();
33         color.b = co.get_blue_p();
34         std::invoke(fn_set, canvas, color);
35         extra_fn();
36     });
37 }
38 
39 struct ViewInfo {
40 public:
41     ActionID action;
42     std::string icon;
43     std::string tooltip;
44     float azimuth;
45     float elevation;
46 };
47 
48 static const std::vector<ViewInfo> views = {
49         {ActionID::VIEW_3D_FRONT, "front", "Front", 270., 0.},
50         {ActionID::VIEW_3D_BACK, "back", "Back", 90., 0.},
51         {ActionID::VIEW_3D_TOP, "top", "Top", 270., 89.99},
52         {ActionID::VIEW_3D_BOTTOM, "bottom", "Bottom", 270., -89.99},
53         {ActionID::VIEW_3D_RIGHT, "right", "Right", 0., 0.},
54         {ActionID::VIEW_3D_LEFT, "left", "Left", 180., 0.},
55 };
56 
View3DWindow(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,const class Board & bo,class IPool & p,Mode md,Canvas3D * ca_custom)57 View3DWindow::View3DWindow(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x, const class Board &bo,
58                            class IPool &p, Mode md, Canvas3D *ca_custom)
59     : Gtk::Window(cobject), board(bo), pool(p), mode(md), state_store(this, "imp-board-3d")
60 {
61     Gtk::Box *gl_box;
62     GET_WIDGET(gl_box);
63 
64     if (ca_custom)
65         canvas = ca_custom;
66     else
67         canvas = Gtk::manage(new Canvas3D);
68     gl_box->pack_start(*canvas, true, true, 0);
69     canvas->show();
70 
71     Gtk::Revealer *revealer;
72     GET_WIDGET(revealer);
73     Gtk::ToggleButton *settings_button;
74     GET_WIDGET(settings_button);
75     settings_button->signal_toggled().connect(
76             [settings_button, revealer] { revealer->set_reveal_child(settings_button->get_active()); });
77 
78     Gtk::Button *update_button;
79     GET_WIDGET(update_button);
80     update_button->signal_clicked().connect([this] { update(); });
81 
82     {
83         Gtk::Box *view_buttons_box;
84         GET_WIDGET(view_buttons_box);
85 
86         for (const auto &it : views) {
87             auto b = Gtk::manage(new Gtk::Button);
88             b->set_image_from_icon_name("view-3d-" + it.icon + "-symbolic", Gtk::ICON_SIZE_BUTTON);
89             b->set_tooltip_text(it.tooltip);
90             b->show();
91             float az = it.azimuth;
92             float el = it.elevation;
93             b->signal_clicked().connect([this, az, el] {
94                 canvas->set_cam_azimuth(az);
95                 canvas->set_cam_elevation(el);
96             });
97             view_buttons_box->pack_start(*b, false, false, 0);
98         }
99     }
100 
101     Gtk::Button *rotate_left_button;
102     Gtk::Button *rotate_right_button;
103     GET_WIDGET(rotate_left_button);
104     GET_WIDGET(rotate_right_button);
105     rotate_left_button->signal_clicked().connect([this] { canvas->inc_cam_azimuth(-90); });
106     rotate_right_button->signal_clicked().connect([this] { canvas->inc_cam_azimuth(90); });
107 
108     Gtk::Button *view_all_button;
109     GET_WIDGET(view_all_button);
110     view_all_button->signal_clicked().connect([this] { canvas->view_all(); });
111 
112     auto explode_adj = Glib::RefPtr<Gtk::Adjustment>::cast_dynamic(x->get_object("explode_adj"));
113     explode_adj->signal_value_changed().connect([explode_adj, this] { canvas->set_explode(explode_adj->get_value()); });
114 
115     GET_WIDGET(background_top_color_button);
116     bind_color_button(background_top_color_button, &Canvas3D::set_background_top_color, [this] {
117         if (!setting_background_color_from_preset && background_color_preset_combo)
118             background_color_preset_combo->set_active(-1);
119     });
120     background_top_color_button->set_color(Gdk::Color("#333365"));
121 
122     GET_WIDGET(background_bottom_color_button);
123     bind_color_button(background_bottom_color_button, &Canvas3D::set_background_bottom_color, [this] {
124         if (!setting_background_color_from_preset && background_color_preset_combo)
125             background_color_preset_combo->set_active(-1);
126     });
127     background_bottom_color_button->set_color(Gdk::Color("#9797aa"));
128 
129     Gtk::Switch *solder_mask_switch;
130     GET_WIDGET(solder_mask_switch);
131     solder_mask_switch->property_active().signal_changed().connect(
132             [this, solder_mask_switch] { canvas->set_show_solder_mask(solder_mask_switch->get_active()); });
133 
134     GET_WIDGET(solder_mask_color_button);
135     bind_color_button(solder_mask_color_button, &Canvas3D::set_solder_mask_color, [this] { s_signal_changed.emit(); });
136     solder_mask_color_button->set_color(Gdk::Color("#008000"));
137 
138 
139     Gtk::Switch *silkscreen_switch;
140     GET_WIDGET(silkscreen_switch);
141     silkscreen_switch->property_active().signal_changed().connect(
142             [this, silkscreen_switch] { canvas->set_show_silkscreen(silkscreen_switch->get_active()); });
143 
144     GET_WIDGET(silkscreen_color_button);
145     bind_color_button(silkscreen_color_button, &Canvas3D::set_silkscreen_color, [this] { s_signal_changed.emit(); });
146     silkscreen_color_button->set_color(Gdk::Color("#FFFFFF"));
147 
148     Gtk::Switch *substrate_switch;
149     GET_WIDGET(substrate_switch);
150     substrate_switch->property_active().signal_changed().connect(
151             [this, substrate_switch] { canvas->set_show_substrate(substrate_switch->get_active()); });
152 
153     GET_WIDGET(substrate_color_button);
154     bind_color_button(substrate_color_button, &Canvas3D::set_substrate_color, [this] { s_signal_changed.emit(); });
155     substrate_color_button->set_color(Gdk::Color("#332600"));
156 
157     Gtk::Switch *paste_switch;
158     GET_WIDGET(paste_switch);
159     paste_switch->property_active().signal_changed().connect(
160             [this, paste_switch] { canvas->set_show_solder_paste(paste_switch->get_active()); });
161 
162     {
163         Gtk::RadioButton *models_none_rb;
164         GET_WIDGET(models_none_rb);
165         models_none_rb->property_active().signal_changed().connect(
166                 [this, models_none_rb] { canvas->set_show_models(!models_none_rb->get_active()); });
167     }
168 
169     {
170         Gtk::RadioButton *models_placed_rb;
171         GET_WIDGET(models_placed_rb);
172         if (mode == Mode::BOARD) {
173             models_placed_rb->property_active().signal_changed().connect(
174                     [this, models_placed_rb] { canvas->set_show_dnp_models(!models_placed_rb->get_active()); });
175         }
176         else {
177             models_placed_rb->hide();
178         }
179     }
180     {
181         Gtk::RadioButton *models_all_rb;
182         GET_WIDGET(models_all_rb);
183         if (mode == Mode::PACKAGE) {
184             models_all_rb->set_active();
185         }
186     }
187 
188     {
189         Gtk::RadioButton *copper_on_rb;
190         GET_WIDGET(copper_on_rb);
191         copper_on_rb->property_active().signal_changed().connect([this, copper_on_rb] {
192             if (copper_on_rb->get_active()) {
193                 canvas->set_show_copper(true);
194                 canvas->set_use_layer_colors(false);
195             }
196         });
197     }
198     {
199         Gtk::RadioButton *copper_layer_colors_rb;
200         GET_WIDGET(copper_layer_colors_rb);
201         copper_layer_colors_rb->property_active().signal_changed().connect([this, copper_layer_colors_rb] {
202             if (copper_layer_colors_rb->get_active()) {
203                 canvas->set_show_copper(true);
204                 canvas->set_use_layer_colors(true);
205             }
206         });
207     }
208     {
209         Gtk::RadioButton *copper_off_rb;
210         GET_WIDGET(copper_off_rb);
211         copper_off_rb->property_active().signal_changed().connect([this, copper_off_rb] {
212             if (copper_off_rb->get_active()) {
213                 canvas->set_show_copper(false);
214             }
215         });
216     }
217 
218 
219     GET_WIDGET(proj_persp_rb);
220     GET_WIDGET(proj_ortho_rb);
221     proj_persp_rb->signal_toggled().connect([this] {
222         canvas->set_projection(proj_persp_rb->get_active() ? Canvas3D::Projection::PERSP : Canvas3D::Projection::ORTHO);
223     });
224 
225     GET_WIDGET(background_color_preset_combo);
226 
227     // gradients from https://uigradients.com/
228     static const std::vector<std::pair<std::string, std::pair<Gdk::Color, Gdk::Color>>> background_color_presets = {
229             {"Default", {Gdk::Color("#333365"), Gdk::Color("#9797aa")}},
230             {"Sunset 1", {Gdk::Color("#333365"), Gdk::Color("#B3A26B")}},
231             {"Sunset 2", {Gdk::Color("#35FFEE"), Gdk::Color("#FFC674")}},
232             {"White", {Gdk::Color("#ffffff"), Gdk::Color("#ffffff")}},
233             {"Black", {Gdk::Color("#000000"), Gdk::Color("#000000")}},
234             {"Grey", {Gdk::Color("#808080"), Gdk::Color("#808080")}},
235             {"Honey Dew", {Gdk::Color("#C33764"), Gdk::Color("#F8FFAE")}},
236             {"80s Sunset", {Gdk::Color("#3494E6"), Gdk::Color("#EC6EAD")}},
237             {"Deep Sea Space", {Gdk::Color("#2C3E50"), Gdk::Color("#4CA1AF")}},
238             {"Dark Skies", {Gdk::Color("#4B79A1"), Gdk::Color("#283E51")}},
239             {"Friday", {Gdk::Color("#83a4d4"), Gdk::Color("#b6fbff")}},
240     };
241     for (const auto &it : background_color_presets) {
242         background_color_preset_combo->append(it.first, it.first);
243     }
244     background_color_preset_combo->set_active_id("Default");
245     background_color_preset_combo->signal_changed().connect([this] {
246         std::string id = background_color_preset_combo->get_active_id();
247         for (const auto &it : background_color_presets) {
248             if (it.first == id) {
249                 setting_background_color_from_preset = true;
250                 background_top_color_button->set_color(it.second.first);
251                 background_bottom_color_button->set_color(it.second.second);
252                 setting_background_color_from_preset = false;
253                 break;
254             }
255         }
256     });
257 
258     GET_WIDGET(model_loading_revealer);
259     GET_WIDGET(model_loading_spinner);
260     GET_WIDGET(model_loading_progress);
261     canvas->signal_models_loading().connect([this](unsigned int i, unsigned int n) {
262         bool loading = i < n;
263         model_loading_revealer->set_reveal_child(loading);
264         model_loading_spinner->property_active() = loading;
265         model_loading_progress->set_visible(n > 1);
266         model_loading_progress->set_fraction(i / (n * 1.0));
267     });
268 
269     Gtk::ComboBoxText *msaa_combo;
270     GET_WIDGET(msaa_combo);
271     msaa_combo->append("0", "Off");
272     for (int i = 1; i < 5; i *= 2) {
273         msaa_combo->append(std::to_string(i), std::to_string(i) + "× MSAA");
274     }
275     msaa_combo->signal_changed().connect([this, msaa_combo] {
276         int msaa = std::stoi(msaa_combo->get_active_id());
277         canvas->set_msaa(msaa);
278     });
279     msaa_combo->set_active_id("4");
280 
281     GET_WIDGET(hud_label);
282     GET_WIDGET(hud_revealer);
283     revealer->signal_size_allocate().connect(
284             [this](Gtk::Allocation &alloc) { hud_revealer->set_margin_start(alloc.get_width() + 50); });
285 
286     canvas->signal_package_select().connect([this](const auto &uu) {
287         if (mode == Mode::BOARD) {
288             hud_set_package(uu);
289             if (uu)
290                 canvas->set_highlights({uu});
291             else
292                 canvas->set_highlights({});
293         }
294     });
295 
296     {
297         Gtk::Box *lollipop_box;
298         GET_WIDGET(lollipop_box);
299         auto axes_lollipop = Gtk::manage(new AxesLollipop());
300         axes_lollipop->show();
301         lollipop_box->pack_start(*axes_lollipop, true, true, 0);
302         canvas->signal_view_changed().connect(sigc::track_obj(
303                 [this, axes_lollipop] {
304                     const float alpha = -glm::radians(canvas->get_cam_azimuth() + 90);
305                     const float beta = glm::radians(canvas->get_cam_elevation() - 90);
306                     axes_lollipop->set_angles(alpha, beta);
307                 },
308                 *axes_lollipop));
309     }
310 
311     canvas->signal_key_press_event().connect(sigc::mem_fun(*this, &View3DWindow::handle_action_key));
312     signal_key_press_event().connect([this](GdkEventKey *ev) {
313         if (canvas->has_focus()) {
314             return false; // handled by canvas
315         }
316         else {
317             return handle_action_key(ev);
318         }
319     });
320 
321     connect_action(ActionID::VIEW_ALL, [this](const auto &conn) { canvas->view_all(); });
322     connect_action(ActionID::PAN_LEFT, sigc::mem_fun(*this, &View3DWindow::handle_pan_action));
323     connect_action(ActionID::PAN_RIGHT, sigc::mem_fun(*this, &View3DWindow::handle_pan_action));
324     connect_action(ActionID::PAN_UP, sigc::mem_fun(*this, &View3DWindow::handle_pan_action));
325     connect_action(ActionID::PAN_DOWN, sigc::mem_fun(*this, &View3DWindow::handle_pan_action));
326 
327     connect_action(ActionID::ZOOM_IN, sigc::mem_fun(*this, &View3DWindow::handle_zoom_action));
328     connect_action(ActionID::ZOOM_OUT, sigc::mem_fun(*this, &View3DWindow::handle_zoom_action));
329 
330     connect_action(ActionID::ROTATE_VIEW_LEFT, sigc::mem_fun(*this, &View3DWindow::handle_rotate_action));
331     connect_action(ActionID::ROTATE_VIEW_RIGHT, sigc::mem_fun(*this, &View3DWindow::handle_rotate_action));
332 
333     connect_action(ActionID::VIEW_3D_FRONT, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
334     connect_action(ActionID::VIEW_3D_BACK, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
335     connect_action(ActionID::VIEW_3D_BOTTOM, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
336     connect_action(ActionID::VIEW_3D_TOP, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
337     connect_action(ActionID::VIEW_3D_LEFT, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
338     connect_action(ActionID::VIEW_3D_RIGHT, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
339 
340     connect_action(ActionID::VIEW_3D_ORTHO, sigc::mem_fun(*this, &View3DWindow::handle_proj_action));
341     connect_action(ActionID::VIEW_3D_PERSP, sigc::mem_fun(*this, &View3DWindow::handle_proj_action));
342 
343 
344     GET_WIDGET(main_box);
345 }
346 
add_widget(Gtk::Widget * w)347 void View3DWindow::add_widget(Gtk::Widget *w)
348 {
349     main_box->pack_start(*w, false, false, 0);
350 }
351 
update(bool clear)352 void View3DWindow::update(bool clear)
353 {
354     s_signal_request_update.emit();
355     canvas->update(board);
356     if (clear)
357         canvas->clear_3d_models();
358     canvas->load_models_async(pool);
359 }
360 
set_highlights(const std::set<UUID> & pkgs)361 void View3DWindow::set_highlights(const std::set<UUID> &pkgs)
362 {
363     canvas->set_highlights(pkgs);
364     if (pkgs.size() == 1) {
365         hud_set_package(*pkgs.begin());
366     }
367     else {
368         hud_set_package(UUID());
369     }
370 }
371 
set_solder_mask_color(const Gdk::RGBA & c)372 void View3DWindow::set_solder_mask_color(const Gdk::RGBA &c)
373 {
374     solder_mask_color_button->set_rgba(c);
375 }
376 
get_solder_mask_color()377 Gdk::RGBA View3DWindow::get_solder_mask_color()
378 {
379     return solder_mask_color_button->get_rgba();
380 }
381 
set_silkscreen_color(const Gdk::RGBA & c)382 void View3DWindow::set_silkscreen_color(const Gdk::RGBA &c)
383 {
384     silkscreen_color_button->set_rgba(c);
385 }
386 
get_silkscreen_color()387 Gdk::RGBA View3DWindow::get_silkscreen_color()
388 {
389     return silkscreen_color_button->get_rgba();
390 }
391 
set_substrate_color(const Gdk::RGBA & c)392 void View3DWindow::set_substrate_color(const Gdk::RGBA &c)
393 {
394     substrate_color_button->set_rgba(c);
395 }
396 
get_substrate_color()397 Gdk::RGBA View3DWindow::get_substrate_color()
398 {
399     return substrate_color_button->get_rgba();
400 }
401 
signal_package_select()402 View3DWindow::type_signal_package_select View3DWindow::signal_package_select()
403 {
404     return canvas->signal_package_select();
405 }
406 
hud_set_package(const UUID & uu)407 void View3DWindow::hud_set_package(const UUID &uu)
408 {
409     if (!uu || board.packages.count(uu) == 0) {
410         hud_revealer->set_reveal_child(false);
411         return;
412     }
413     const auto &pkg = board.packages.at(uu);
414     if (pkg.component) {
415         hud_revealer->set_reveal_child(true);
416         auto part = pkg.component->part;
417         std::string s = "<b>" + pkg.component->refdes + ":</b> ";
418         s += Glib::Markup::escape_text(part->get_value()) + "\n";
419         if (part->get_value() != part->get_MPN()) {
420             s += "MPN: " + Glib::Markup::escape_text(part->get_MPN()) + "\n";
421         }
422         s += "Manufacturer: " + Glib::Markup::escape_text(part->get_manufacturer()) + "\n";
423         s += "Package: " + Glib::Markup::escape_text(part->package->name) + "\n";
424         if (part->get_description().size())
425             s += Glib::Markup::escape_text(part->get_description()) + "\n";
426         if (part->get_datasheet().size())
427             s += "<a href=\"" + Glib::Markup::escape_text(part->get_datasheet()) + "\" title=\""
428                  + Glib::Markup::escape_text(Glib::Markup::escape_text(part->get_datasheet())) + "\">Datasheet</a>\n";
429         trim(s);
430         hud_label->set_markup(s);
431     }
432 }
433 
handle_action_key(const GdkEventKey * ev)434 bool View3DWindow::handle_action_key(const GdkEventKey *ev)
435 {
436     if (ev->is_modifier)
437         return false;
438     if (ev->keyval == GDK_KEY_Escape) {
439         if (keys_current.size() == 0) {
440             s_signal_present_imp.emit();
441             return true;
442         }
443         else {
444             keys_current.clear();
445             return true;
446         }
447     }
448     else {
449         auto display = get_display()->gobj();
450         auto hw_keycode = ev->hardware_keycode;
451         auto state = static_cast<GdkModifierType>(ev->state);
452         auto group = ev->group;
453         guint keyval;
454         GdkModifierType consumed_modifiers;
455         if (gdk_keymap_translate_keyboard_state(gdk_keymap_get_for_display(display), hw_keycode, state, group, &keyval,
456                                                 NULL, NULL, &consumed_modifiers)) {
457             auto mod = static_cast<GdkModifierType>((state & (~consumed_modifiers))
458                                                     & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK));
459             keys_current.emplace_back(keyval, mod);
460         }
461         std::map<ActionConnection *, std::pair<KeyMatchResult, KeySequence>> connections_matched;
462         for (auto &it : action_connections) {
463             auto k = std::make_pair(it.second.action_id, it.second.tool_id);
464             if (action_catalog.at(k).availability & ActionCatalogItem::AVAILABLE_IN_3D) {
465                 bool can_begin = true;
466                 if (can_begin) {
467                     for (const auto &it2 : it.second.key_sequences) {
468                         if (const auto m = key_sequence_match(keys_current, it2); m != KeyMatchResult::NONE) {
469                             connections_matched.emplace(std::piecewise_construct, std::forward_as_tuple(&it.second),
470                                                         std::forward_as_tuple(m, it2));
471                         }
472                     }
473                 }
474             }
475         }
476 
477 
478         if (connections_matched.size() == 1) {
479             keys_current.clear();
480             auto conn = connections_matched.begin()->first;
481             trigger_action(conn->action_id);
482             return true;
483         }
484 
485         else if (connections_matched.size() > 1) { // still ambigous
486             std::list<std::pair<std::string, KeySequence>> conflicts;
487             bool have_conflict = false;
488             for (const auto &[conn, it] : connections_matched) {
489                 const auto &[res, seq] = it;
490                 if (res == KeyMatchResult::COMPLETE) {
491                     have_conflict = true;
492                 }
493                 conflicts.emplace_back(action_catalog.at(std::make_pair(conn->action_id, conn->tool_id)).name, seq);
494             }
495             if (have_conflict) {
496                 keys_current.clear();
497                 return false;
498             }
499             return true;
500         }
501         else if (connections_matched.size() == 0) {
502             keys_current.clear();
503             return false;
504         }
505         else {
506             return false;
507         }
508     }
509     return false;
510 }
511 
trigger_action(ActionID action)512 void View3DWindow::trigger_action(ActionID action)
513 {
514     auto conn = action_connections.at(action);
515     conn.cb(conn);
516 }
517 
connect_action(ActionID action_id,std::function<void (const ActionConnection &)> cb)518 ActionConnection &View3DWindow::connect_action(ActionID action_id, std::function<void(const ActionConnection &)> cb)
519 {
520     if (action_connections.count(action_id)) {
521         throw std::runtime_error("duplicate action");
522     }
523     if (action_catalog.count(make_action(action_id)) == 0) {
524         throw std::runtime_error("invalid action");
525     }
526     auto &act = action_connections
527                         .emplace(std::piecewise_construct, std::forward_as_tuple(action_id),
528                                  std::forward_as_tuple(make_action(action_id), cb))
529                         .first->second;
530 
531     return act;
532 }
533 
apply_preferences(const Preferences & prefs)534 void View3DWindow::apply_preferences(const Preferences &prefs)
535 {
536     canvas->smooth_zoom = prefs.zoom.smooth_zoom_3d;
537     canvas->set_appearance(prefs.canvas_layer.appearance);
538     const auto av = ActionCatalogItem::AVAILABLE_IN_3D;
539     for (auto &it : action_connections) {
540         const auto k = make_action(it.first);
541         auto act = action_catalog.at(k);
542         if (!(act.flags & ActionCatalogItem::FLAGS_NO_PREFERENCES) && prefs.key_sequences.keys.count(k)) {
543             const auto &pref = prefs.key_sequences.keys.at(k);
544             const std::vector<KeySequence> *seqs = nullptr;
545             if (pref.count(av) && pref.at(av).size()) {
546                 seqs = &pref.at(av);
547             }
548             else if (pref.count(ActionCatalogItem::AVAILABLE_EVERYWHERE)
549                      && pref.at(ActionCatalogItem::AVAILABLE_EVERYWHERE).size()) {
550                 seqs = &pref.at(ActionCatalogItem::AVAILABLE_EVERYWHERE);
551             }
552             if (seqs) {
553                 it.second.key_sequences = *seqs;
554             }
555             else {
556                 it.second.key_sequences.clear();
557             }
558         }
559     }
560 }
561 
handle_pan_action(const ActionConnection & c)562 void View3DWindow::handle_pan_action(const ActionConnection &c)
563 {
564     Coordf d;
565     switch (c.action_id) {
566     case ActionID::PAN_DOWN:
567         d.y = 1;
568         break;
569 
570     case ActionID::PAN_UP:
571         d.y = -1;
572         break;
573 
574     case ActionID::PAN_LEFT:
575         d.x = 1;
576         break;
577 
578     case ActionID::PAN_RIGHT:
579         d.x = -1;
580         break;
581     default:
582         return;
583     }
584     d = d * 50;
585     auto center = canvas->get_center();
586     auto shift = canvas->get_center_shift({d.x, d.y});
587     canvas->set_center(center + shift);
588 }
589 
handle_zoom_action(const ActionConnection & conn)590 void View3DWindow::handle_zoom_action(const ActionConnection &conn)
591 {
592     auto inc = 1;
593     if (conn.action_id == ActionID::ZOOM_IN)
594         inc = -1;
595 
596     canvas->set_cam_distance(canvas->get_cam_distance() * pow(1.5, inc));
597 }
598 
handle_rotate_action(const ActionConnection & conn)599 void View3DWindow::handle_rotate_action(const ActionConnection &conn)
600 {
601     auto inc = 90;
602     if (conn.action_id == ActionID::ROTATE_VIEW_LEFT)
603         inc = -90;
604     canvas->inc_cam_azimuth(inc);
605 }
606 
handle_view_action(const ActionConnection & conn)607 void View3DWindow::handle_view_action(const ActionConnection &conn)
608 {
609     for (const auto &it : views) {
610         if (it.action == conn.action_id) {
611             canvas->set_cam_azimuth(it.azimuth);
612             canvas->set_cam_elevation(it.elevation);
613             return;
614         }
615     }
616 }
617 
handle_proj_action(const ActionConnection & conn)618 void View3DWindow::handle_proj_action(const ActionConnection &conn)
619 {
620     if (conn.action_id == ActionID::VIEW_3D_ORTHO)
621         proj_ortho_rb->set_active(true);
622     else
623         proj_persp_rb->set_active(true);
624 }
625 
626 
627 } // namespace horizon
628