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