1 #include "imp.hpp"
2 #include "core/tool_id.hpp"
3 #include "logger/logger.hpp"
4 #include "in_tool_action_catalog.hpp"
5 #include "actions.hpp"
6 #include "util/str_util.hpp"
7
8 namespace horizon {
9
10
11 class KeyLabel : public Gtk::Box {
12 public:
KeyLabel(Glib::RefPtr<Gtk::SizeGroup> sg,const std::string & key_markup,ActionToolID act)13 KeyLabel(Glib::RefPtr<Gtk::SizeGroup> sg, const std::string &key_markup, ActionToolID act)
14 : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 8), action(act)
15 {
16 {
17 auto la = Gtk::manage(new Gtk::Label);
18 la->set_xalign(0);
19 la->set_markup(key_markup);
20 la->show();
21 pack_start(*la, false, false, 0);
22 sg->add_widget(*la);
23 }
24 {
25 auto la = Gtk::manage(new Gtk::Label);
26 la->set_xalign(0);
27 la->set_text(action_catalog.at(action).name);
28 la->show();
29 pack_start(*la, true, true, 0);
30 }
31 property_margin() = 2;
32 }
33
34 const ActionToolID action;
35 };
36
init_key()37 void ImpBase::init_key()
38 {
39 canvas->signal_key_press_event().connect(sigc::mem_fun(*this, &ImpBase::handle_key_press));
40 key_sequence_dialog = std::make_unique<KeySequenceDialog>(this->main_window);
41 connect_action(ActionID::HELP, [this](const auto &a) { key_sequence_dialog->show(); });
42 main_window->key_hint_box->signal_row_activated().connect([this](auto row) {
43 if (auto la = dynamic_cast<KeyLabel *>(row->get_child())) {
44 trigger_action(la->action);
45 keys_current.clear();
46 main_window->key_hint_set_visible(false);
47 }
48 });
49 }
50
handle_key_press(const GdkEventKey * key_event)51 bool ImpBase::handle_key_press(const GdkEventKey *key_event)
52 {
53 return handle_action_key(key_event);
54 return false;
55 }
56
keys_match(const KeySequence & keys) const57 KeyMatchResult ImpBase::keys_match(const KeySequence &keys) const
58 {
59 return key_sequence_match(keys_current, keys);
60 }
61
62 class KeyConflictDialog : public Gtk::MessageDialog {
63 public:
64 KeyConflictDialog(Gtk::Window *parent, const std::list<std::pair<std::string, KeySequence>> &conflicts);
65 enum Response { RESPONSE_PREFS = 1 };
66
67 private:
68 class ListColumns : public Gtk::TreeModelColumnRecord {
69 public:
ListColumns()70 ListColumns()
71 {
72 Gtk::TreeModelColumnRecord::add(name);
73 Gtk::TreeModelColumnRecord::add(keys);
74 }
75 Gtk::TreeModelColumn<Glib::ustring> name;
76 Gtk::TreeModelColumn<Glib::ustring> keys;
77 };
78 ListColumns list_columns;
79
80 Glib::RefPtr<Gtk::ListStore> store;
81
82 Gtk::TreeView *tv = nullptr;
83 };
84
85
KeyConflictDialog(Gtk::Window * parent,const std::list<std::pair<std::string,KeySequence>> & conflicts)86 KeyConflictDialog::KeyConflictDialog(Gtk::Window *parent,
87 const std::list<std::pair<std::string, KeySequence>> &conflicts)
88 : Gtk::MessageDialog(*parent, "Key sequences conflict", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK)
89 {
90 set_secondary_text("The key sequences listed below conflict with each other");
91 add_button("Open preferences", RESPONSE_PREFS);
92
93 store = Gtk::ListStore::create(list_columns);
94
95 tv = Gtk::manage(new Gtk::TreeView(store));
96 tv->get_selection()->set_mode(Gtk::SELECTION_NONE);
97 tv->append_column("Action", list_columns.name);
98 tv->append_column("Key sequence", list_columns.keys);
99
100
101 auto sc = Gtk::manage(new Gtk::ScrolledWindow);
102 sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
103 sc->set_shadow_type(Gtk::SHADOW_IN);
104 sc->property_margin() = 10;
105 sc->set_propagate_natural_height(true);
106 sc->add(*tv);
107
108 for (const auto &[act, keys] : conflicts) {
109 Gtk::TreeModel::Row row = *store->append();
110 row[list_columns.name] = act;
111 row[list_columns.keys] = key_sequence_to_string(keys);
112 }
113
114 get_content_area()->pack_start(*sc, true, true, 0);
115 get_content_area()->show_all();
116 get_content_area()->set_spacing(0);
117 }
118
handle_action_key(const GdkEventKey * ev)119 bool ImpBase::handle_action_key(const GdkEventKey *ev)
120 {
121 if (ev->is_modifier)
122 return false;
123 if (ev->keyval == GDK_KEY_Escape) {
124 if (!core->tool_is_active()) {
125 canvas->set_selection_mode(CanvasGL::SelectionMode::HOVER);
126 canvas->set_selection({});
127 set_search_mode(false);
128
129 reset_tool_hint_label();
130 if (keys_current.size() == 0) {
131 return false;
132 }
133 else {
134 main_window->key_hint_set_visible(false);
135 keys_current.clear();
136 return true;
137 }
138 }
139 else {
140 ToolArgs args;
141 args.coords = canvas->get_cursor_pos();
142 args.work_layer = canvas->property_work_layer();
143 args.type = ToolEventType::ACTION;
144 args.action = InToolActionID::CANCEL;
145 ToolResponse r = core->tool_update(args);
146 tool_process(r);
147 return true;
148 }
149 }
150 else {
151 auto display = main_window->get_display()->gobj();
152 auto hw_keycode = ev->hardware_keycode;
153 auto state = static_cast<GdkModifierType>(ev->state);
154 auto group = ev->group;
155 guint keyval;
156 GdkModifierType consumed_modifiers;
157 if (gdk_keymap_translate_keyboard_state(gdk_keymap_get_for_display(display), hw_keycode, state, group, &keyval,
158 NULL, NULL, &consumed_modifiers)) {
159 auto mod = static_cast<GdkModifierType>((state & (~consumed_modifiers))
160 & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK));
161 keys_current.emplace_back(keyval, mod);
162 }
163 auto in_tool_actions = core->get_tool_actions();
164 std::map<InToolActionID, std::pair<KeyMatchResult, KeySequence>> in_tool_actions_matched;
165 std::map<ActionConnection *, std::pair<KeyMatchResult, KeySequence>> connections_matched;
166 auto selection = canvas->get_selection();
167 update_action_sensitivity();
168 for (auto &it : action_connections) {
169 auto k = std::make_pair(it.second.action_id, it.second.tool_id);
170 if (action_catalog.at(k).availability & get_editor_type_for_action()) {
171 bool can_begin = false;
172 if (it.second.action_id == ActionID::TOOL && !core->tool_is_active()) {
173 can_begin = core->tool_can_begin(it.second.tool_id, selection).first;
174 }
175 else if (it.second.tool_id == ToolID::NONE) {
176 can_begin = get_action_sensitive(k);
177 }
178 if (can_begin) {
179 for (const auto &it2 : it.second.key_sequences) {
180 if (const auto m = keys_match(it2); m != KeyMatchResult::NONE) {
181 connections_matched.emplace(std::piecewise_construct, std::forward_as_tuple(&it.second),
182 std::forward_as_tuple(m, it2));
183 }
184 }
185 }
186 }
187 }
188 if (in_tool_actions.size()) {
189 for (const auto &[action, seqs] : in_tool_key_sequeces_preferences.keys) {
190 if (in_tool_actions.count(action)) {
191 for (const auto &seq : seqs) {
192 if (const auto m = keys_match(seq); m != KeyMatchResult::NONE)
193 in_tool_actions_matched.emplace(std::piecewise_construct, std::forward_as_tuple(action),
194 std::forward_as_tuple(m, seq));
195 }
196 }
197 }
198 }
199
200 if (connections_matched.size() == 1 && in_tool_actions_matched.size() == 1) {
201 main_window->tool_hint_label->set_text("Ambiguous");
202 keys_current.clear();
203 }
204 else if (connections_matched.size() == 1) {
205 main_window->tool_hint_label->set_text(key_sequence_to_string(keys_current));
206 keys_current.clear();
207 main_window->key_hint_set_visible(false);
208 auto conn = connections_matched.begin()->first;
209 if (!trigger_action({conn->action_id, conn->tool_id})) {
210 reset_tool_hint_label();
211 return false;
212 }
213 return true;
214 }
215 else if (in_tool_actions_matched.size() == 1) {
216 main_window->tool_hint_label->set_text(key_sequence_to_string(keys_current));
217 keys_current.clear();
218
219 ToolArgs args;
220 args.coords = canvas->get_cursor_pos();
221 args.work_layer = canvas->property_work_layer();
222 args.type = ToolEventType::ACTION;
223 args.action = in_tool_actions_matched.begin()->first;
224 ToolResponse r = core->tool_update(args);
225 tool_process(r);
226
227 return true;
228 }
229 else if (connections_matched.size() > 1 || in_tool_actions_matched.size() > 1) { // still ambigous
230 std::list<std::pair<std::string, KeySequence>> conflicts;
231 bool have_conflict = false;
232 for (const auto &[conn, it] : connections_matched) {
233 const auto &[res, seq] = it;
234 if (res == KeyMatchResult::COMPLETE) {
235 have_conflict = true;
236 }
237 conflicts.emplace_back(action_catalog.at(std::make_pair(conn->action_id, conn->tool_id)).name, seq);
238 }
239 for (const auto &[act, it] : in_tool_actions_matched) {
240 const auto &[res, seq] = it;
241 if (res == KeyMatchResult::COMPLETE) {
242 have_conflict = true;
243 }
244 conflicts.emplace_back(in_tool_action_catalog.at(act).name + " (in-tool action)", seq);
245 }
246 if (have_conflict) {
247 main_window->tool_hint_label->set_text("Key sequences conflict");
248 keys_current.clear();
249 KeyConflictDialog dia(main_window, conflicts);
250 if (dia.run() == KeyConflictDialog::RESPONSE_PREFS) {
251 show_preferences("keys");
252 }
253 return false;
254 }
255
256 for (auto ch : main_window->key_hint_box->get_children()) {
257 delete ch;
258 }
259
260 for (const auto &[conn, it] : connections_matched) {
261 const auto &[res, seq] = it;
262 std::string seq_label;
263 for (size_t i = 0; i < seq.size(); i++) {
264 seq_label += Glib::Markup::escape_text(key_sequence_item_to_string(seq.at(i))) + " ";
265 if (i + 1 == keys_current.size()) {
266 seq_label += "<b>";
267 }
268 }
269 rtrim(seq_label);
270 seq_label += "</b>";
271
272 auto la = Gtk::manage(
273 new KeyLabel(main_window->key_hint_size_group, seq_label, {conn->action_id, conn->tool_id}));
274 main_window->key_hint_box->append(*la);
275 la->show();
276 }
277 main_window->key_hint_set_visible(true);
278
279 main_window->tool_hint_label->set_text(key_sequence_to_string(keys_current) + "?");
280 return true;
281 }
282 else if (connections_matched.size() == 0 || in_tool_actions_matched.size() == 0) {
283 main_window->tool_hint_label->set_text("Unknown key sequence");
284 keys_current.clear();
285 main_window->key_hint_set_visible(false);
286 return false;
287 }
288 else {
289 Logger::log_warning("Key sequence??", Logger::Domain::IMP,
290 std::to_string(connections_matched.size()) + " "
291 + std::to_string(in_tool_actions_matched.size()));
292 return false;
293 }
294 }
295 return false;
296 }
297
apply_arrow_keys()298 void ImpBase::apply_arrow_keys()
299 {
300 const auto &canvas_prefs = get_canvas_preferences();
301
302 const auto left = GDK_KEY_Left;
303 const auto right = GDK_KEY_Right;
304 const auto up = GDK_KEY_Up;
305 const auto down = GDK_KEY_Down;
306
307 {
308 const auto mod0 = static_cast<GdkModifierType>(0);
309 action_connections.at(make_action(ToolID::MOVE_KEY_LEFT)).key_sequences = {{{left, mod0}}};
310 action_connections.at(make_action(ToolID::MOVE_KEY_RIGHT)).key_sequences = {{{right, mod0}}};
311 action_connections.at(make_action(ToolID::MOVE_KEY_UP)).key_sequences = {{{up, mod0}}};
312 action_connections.at(make_action(ToolID::MOVE_KEY_DOWN)).key_sequences = {{{down, mod0}}};
313
314 in_tool_key_sequeces_preferences.keys[InToolActionID::MOVE_LEFT] = {{{left, mod0}}};
315 in_tool_key_sequeces_preferences.keys[InToolActionID::MOVE_RIGHT] = {{{right, mod0}}};
316 in_tool_key_sequeces_preferences.keys[InToolActionID::MOVE_UP] = {{{up, mod0}}};
317 in_tool_key_sequeces_preferences.keys[InToolActionID::MOVE_DOWN] = {{{down, mod0}}};
318 }
319 {
320 GdkModifierType grid_fine_modifier = GDK_MOD1_MASK;
321
322 switch (canvas_prefs.appearance.grid_fine_modifier) {
323 case Appearance::GridFineModifier::ALT:
324 grid_fine_modifier = GDK_MOD1_MASK;
325 break;
326 case Appearance::GridFineModifier::CTRL:
327 grid_fine_modifier = GDK_CONTROL_MASK;
328 break;
329 }
330 action_connections.at(make_action(ToolID::MOVE_KEY_FINE_LEFT)).key_sequences = {{{left, grid_fine_modifier}}};
331 action_connections.at(make_action(ToolID::MOVE_KEY_FINE_RIGHT)).key_sequences = {{{right, grid_fine_modifier}}};
332 action_connections.at(make_action(ToolID::MOVE_KEY_FINE_UP)).key_sequences = {{{up, grid_fine_modifier}}};
333 action_connections.at(make_action(ToolID::MOVE_KEY_FINE_DOWN)).key_sequences = {{{down, grid_fine_modifier}}};
334 in_tool_key_sequeces_preferences.keys[InToolActionID::MOVE_LEFT_FINE] = {{{left, grid_fine_modifier}}};
335 in_tool_key_sequeces_preferences.keys[InToolActionID::MOVE_RIGHT_FINE] = {{{right, grid_fine_modifier}}};
336 in_tool_key_sequeces_preferences.keys[InToolActionID::MOVE_UP_FINE] = {{{up, grid_fine_modifier}}};
337 in_tool_key_sequeces_preferences.keys[InToolActionID::MOVE_DOWN_FINE] = {{{down, grid_fine_modifier}}};
338 }
339 }
340
341 } // namespace horizon
342