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