1 #include "imp.hpp"
2 #include "widgets/action_button.hpp"
3 #include "core/tool_id.hpp"
4 #include "actions.hpp"
5 #include "util/util.hpp"
6 #include "in_tool_action_catalog.hpp"
7 #include "logger/logger.hpp"
8 
9 namespace horizon {
init_action()10 void ImpBase::init_action()
11 {
12     for (const auto &it : action_catalog) {
13         if ((it.first.first == ActionID::TOOL) && (it.second.availability & get_editor_type_for_action())) {
14             connect_action(it.first.second);
15         }
16     }
17     connect_action(ActionID::PAN_LEFT, sigc::mem_fun(*this, &ImpBase::handle_pan_action));
18     connect_action(ActionID::PAN_RIGHT, sigc::mem_fun(*this, &ImpBase::handle_pan_action));
19     connect_action(ActionID::PAN_UP, sigc::mem_fun(*this, &ImpBase::handle_pan_action));
20     connect_action(ActionID::PAN_DOWN, sigc::mem_fun(*this, &ImpBase::handle_pan_action));
21 
22     connect_action(ActionID::ZOOM_IN, sigc::mem_fun(*this, &ImpBase::handle_zoom_action));
23     connect_action(ActionID::ZOOM_OUT, sigc::mem_fun(*this, &ImpBase::handle_zoom_action));
24 }
25 
add_action_button(ActionToolID action)26 ActionButton &ImpBase::add_action_button(ActionToolID action)
27 {
28     main_window->set_use_action_bar(true);
29     auto ab = Gtk::manage(new ActionButton(action, action_connections));
30     ab->show();
31     ab->signal_action().connect([this](auto act) {
32         force_end_tool();
33         this->trigger_action(act);
34     });
35     main_window->action_bar_box->pack_start(*ab, false, false, 0);
36     action_buttons.push_back(ab);
37     return *ab;
38 }
39 
add_action_button_menu(const char * icon_name)40 ActionButtonMenu &ImpBase::add_action_button_menu(const char *icon_name)
41 {
42     main_window->set_use_action_bar(true);
43     auto ab = Gtk::manage(new ActionButtonMenu(icon_name, action_connections));
44     ab->show();
45     ab->signal_action().connect([this](auto act) {
46         force_end_tool();
47         this->trigger_action(act);
48     });
49     main_window->action_bar_box->pack_start(*ab, false, false, 0);
50     action_buttons.push_back(ab);
51     return *ab;
52 }
53 
add_action_button_polygon()54 ActionButton &ImpBase::add_action_button_polygon()
55 {
56     auto &b = add_action_button(make_action(ToolID::DRAW_POLYGON));
57     b.add_action(make_action(ToolID::DRAW_POLYGON_RECTANGLE));
58     b.add_action(make_action(ToolID::DRAW_POLYGON_CIRCLE));
59     return b;
60 }
61 
add_action_button_line()62 ActionButton &ImpBase::add_action_button_line()
63 {
64     auto &b = add_action_button(make_action(ToolID::DRAW_LINE));
65     b.add_action(make_action(ToolID::DRAW_LINE_RECTANGLE));
66     b.add_action(make_action(ToolID::DRAW_LINE_CIRCLE));
67     b.add_action(make_action(ToolID::DRAW_ARC));
68     return b;
69 }
70 
tool_bar_set_actions(const std::vector<ActionLabelInfo> & labels)71 void ImpBase::tool_bar_set_actions(const std::vector<ActionLabelInfo> &labels)
72 {
73     if (in_tool_action_label_infos != labels) {
74         tool_bar_clear_actions();
75         for (const auto &it : labels) {
76             tool_bar_append_action(it.action1, it.action2, it.label);
77         }
78 
79         in_tool_action_label_infos = labels;
80     }
81 }
82 
tool_bar_append_action(InToolActionID action1,InToolActionID action2,const std::string & s)83 void ImpBase::tool_bar_append_action(InToolActionID action1, InToolActionID action2, const std::string &s)
84 {
85     auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 5));
86     if (any_of(action1, {InToolActionID::LMB, InToolActionID::RMB})) {
87         std::string icon_name = "action-";
88         if (action1 == InToolActionID::LMB) {
89             icon_name += "lmb";
90         }
91         else {
92             icon_name += "rmb";
93         }
94         icon_name += "-symbolic";
95         auto img = Gtk::manage(new Gtk::Image);
96         img->set_from_icon_name(icon_name, Gtk::ICON_SIZE_BUTTON);
97         img->show();
98         box->pack_start(*img, false, false, 0);
99     }
100     else {
101         auto key_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
102         for (const auto action : {action1, action2}) {
103             if (action != InToolActionID::NONE) {
104                 const auto &prefs = in_tool_key_sequeces_preferences.keys;
105                 if (prefs.count(action)) {
106                     if (prefs.at(action).size()) {
107                         auto seq = prefs.at(action).front();
108                         auto kl = Gtk::manage(new Gtk::Label(key_sequence_to_string_short(seq)));
109                         kl->set_valign(Gtk::ALIGN_BASELINE);
110                         key_box->pack_start(*kl, false, false, 0);
111                     }
112                 }
113             }
114         }
115         key_box->show_all();
116         key_box->get_style_context()->add_class("imp-key-box");
117 
118         box->pack_start(*key_box, false, false, 0);
119     }
120     const auto &as = s.size() ? s : in_tool_action_catalog.at(action1).name;
121 
122     auto la = Gtk::manage(new Gtk::Label(as));
123     la->set_valign(Gtk::ALIGN_BASELINE);
124 
125     la->show();
126 
127     box->pack_start(*la, false, false, 0);
128 
129     main_window->tool_bar_append_action(*box);
130 }
131 
tool_bar_clear_actions()132 void ImpBase::tool_bar_clear_actions()
133 {
134     in_tool_action_label_infos.clear();
135     main_window->tool_bar_clear_actions();
136 }
137 
138 
add_tool_button(ToolID id,const std::string & label,bool left)139 void ImpBase::add_tool_button(ToolID id, const std::string &label, bool left)
140 {
141     auto button = Gtk::manage(new Gtk::Button(label));
142     button->signal_clicked().connect([this, id] { tool_begin(id); });
143     button->show();
144     core->signal_tool_changed().connect([button](ToolID t) { button->set_sensitive(t == ToolID::NONE); });
145     if (left)
146         main_window->header->pack_start(*button);
147     else
148         main_window->header->pack_end(*button);
149 }
150 
add_tool_action(ToolID tid,const std::string & action)151 void ImpBase::add_tool_action(ToolID tid, const std::string &action)
152 {
153     auto tool_action = main_window->add_action(action, [this, tid] { tool_begin(tid); });
154 }
155 
add_tool_action(ActionID aid,const std::string & action)156 void ImpBase::add_tool_action(ActionID aid, const std::string &action)
157 {
158     auto tool_action = main_window->add_action(action, [this, aid] { trigger_action(aid); });
159 }
160 
set_action_sensitive(ActionToolID action,bool v)161 void ImpBase::set_action_sensitive(ActionToolID action, bool v)
162 {
163     action_sensitivity[action] = v;
164     s_signal_action_sensitive.emit();
165 }
166 
get_action_sensitive(ActionToolID action) const167 bool ImpBase::get_action_sensitive(ActionToolID action) const
168 {
169     if (core->tool_is_active())
170         return action_catalog.at(action).flags & ActionCatalogItem::FLAGS_IN_TOOL;
171     if (action_sensitivity.count(action))
172         return action_sensitivity.at(action);
173     else
174         return true;
175 }
176 
update_action_sensitivity()177 void ImpBase::update_action_sensitivity()
178 {
179     set_action_sensitive(make_action(ActionID::SAVE), !read_only && core->get_needs_save());
180     set_action_sensitive(make_action(ActionID::UNDO), core->can_undo());
181     set_action_sensitive(make_action(ActionID::REDO), core->can_redo());
182     auto sel = canvas->get_selection();
183     set_action_sensitive(make_action(ActionID::COPY), sel.size() > 0);
184     bool can_select_polygon = std::any_of(sel.begin(), sel.end(), [](const auto &x) {
185         switch (x.type) {
186         case ObjectType::POLYGON_ARC_CENTER:
187         case ObjectType::POLYGON_EDGE:
188         case ObjectType::POLYGON_VERTEX:
189             return true;
190 
191         default:
192             return false;
193         }
194     });
195     set_action_sensitive(make_action(ActionID::SELECT_POLYGON), can_select_polygon);
196 }
197 
198 
create_action_button(ActionToolID action)199 Gtk::Button *ImpBase::create_action_button(ActionToolID action)
200 {
201     auto &catitem = action_catalog.at(action);
202     auto button = Gtk::manage(new Gtk::Button(catitem.name));
203     signal_action_sensitive().connect([this, button, action] { button->set_sensitive(get_action_sensitive(action)); });
204     button->signal_clicked().connect([this, action] { trigger_action(action); });
205     button->set_sensitive(get_action_sensitive(action));
206     return button;
207 }
208 
trigger_action(const ActionToolID & action)209 bool ImpBase::trigger_action(const ActionToolID &action)
210 {
211     if (core->tool_is_active() && !(action_catalog.at(action).flags & ActionCatalogItem::FLAGS_IN_TOOL)) {
212         return false;
213     }
214     main_window->key_hint_set_visible(false);
215     if (keys_current.size()) {
216         keys_current.clear();
217         reset_tool_hint_label();
218     }
219     auto conn = action_connections.at(action);
220     conn.cb(conn);
221     return true;
222 }
223 
trigger_action(ActionID aid)224 bool ImpBase::trigger_action(ActionID aid)
225 {
226     return trigger_action({aid, ToolID::NONE});
227 }
228 
trigger_action(ToolID tid)229 bool ImpBase::trigger_action(ToolID tid)
230 {
231     return trigger_action({ActionID::TOOL, tid});
232 }
233 
handle_tool_action(const ActionConnection & conn)234 void ImpBase::handle_tool_action(const ActionConnection &conn)
235 {
236     assert(conn.action_id == ActionID::TOOL);
237     tool_begin(conn.tool_id);
238 }
239 
240 
connect_action(ActionID action_id,std::function<void (const ActionConnection &)> cb)241 ActionConnection &ImpBase::connect_action(ActionID action_id, std::function<void(const ActionConnection &)> cb)
242 {
243     return connect_action(action_id, ToolID::NONE, cb);
244 }
245 
connect_action(ToolID tool_id,std::function<void (const ActionConnection &)> cb)246 ActionConnection &ImpBase::connect_action(ToolID tool_id, std::function<void(const ActionConnection &)> cb)
247 {
248     return connect_action(ActionID::TOOL, tool_id, cb);
249 }
250 
connect_action(ToolID tool_id)251 ActionConnection &ImpBase::connect_action(ToolID tool_id)
252 {
253     return connect_action(tool_id, sigc::mem_fun(*this, &ImpBase::handle_tool_action));
254 }
255 
connect_action(ActionID action_id,ToolID tool_id,std::function<void (const ActionConnection &)> cb)256 ActionConnection &ImpBase::connect_action(ActionID action_id, ToolID tool_id,
257                                           std::function<void(const ActionConnection &)> cb)
258 {
259     const auto key = std::make_pair(action_id, tool_id);
260     if (action_connections.count(key)) {
261         throw std::runtime_error("duplicate action");
262     }
263     if (action_catalog.count(key) == 0) {
264         throw std::runtime_error("invalid action");
265     }
266     auto &act = action_connections
267                         .emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(key, cb))
268                         .first->second;
269 
270     return act;
271 }
272 
handle_pan_action(const ActionConnection & c)273 void ImpBase::handle_pan_action(const ActionConnection &c)
274 {
275     Coordf d;
276     switch (c.action_id) {
277     case ActionID::PAN_DOWN:
278         d.y = -1;
279         break;
280 
281     case ActionID::PAN_UP:
282         d.y = 1;
283         break;
284 
285     case ActionID::PAN_LEFT:
286         d.x = 1;
287         break;
288 
289     case ActionID::PAN_RIGHT:
290         d.x = -1;
291         break;
292     default:
293         return;
294     }
295     auto [sc, offset] = canvas->get_scale_and_offset();
296     offset += d * 50;
297     canvas->set_scale_and_offset(sc, offset);
298 }
299 
handle_zoom_action(const ActionConnection & conn)300 void ImpBase::handle_zoom_action(const ActionConnection &conn)
301 {
302     auto inc = 1;
303     if (conn.action_id == ActionID::ZOOM_OUT)
304         inc = -1;
305 
306     Coordf c;
307     if (preferences.zoom.keyboard_zoom_to_cursor)
308         c = canvas->get_cursor_pos_win();
309     else
310         c = Coordf(canvas->get_width(), canvas->get_height()) / 2;
311     canvas->zoom_to(c, inc);
312 }
313 
force_end_tool()314 void ImpBase::force_end_tool()
315 {
316     if (!core->tool_is_active())
317         return;
318 
319     for (auto i = 0; i < 5; i++) {
320         ToolArgs args;
321         args.coords = canvas->get_cursor_pos();
322         args.work_layer = canvas->property_work_layer();
323         args.type = ToolEventType::ACTION;
324         args.action = InToolActionID::CANCEL;
325         ToolResponse r = core->tool_update(args);
326         tool_process(r);
327         if (!core->tool_is_active())
328             return;
329     }
330     Logger::get().log_critical("Tool didn't end", Logger::Domain::IMP, "end the tool and repeat the last action");
331 }
332 
333 } // namespace horizon
334