1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2 
3 #include "smshortcut.hh"
4 #include "smleakdebugger.hh"
5 #include "smdialog.hh"
6 #include "smfixedgrid.hh"
7 #include "smlabel.hh"
8 #include "smbutton.hh"
9 
10 using namespace SpectMorph;
11 
12 using std::string;
13 
14 static LeakDebugger leak_debugger ("SpectMorph::Shortcut");
15 
16 /* In this implementation, shortcuts belong to a window (not a widget).
17  *
18  * This means:
19  *  - a shortcut will get deleted if the window gets deleted
20  *  - if you want to get rid of the shortcut earlier, you can delete it
21  */
22 Shortcut::Shortcut (Window *window, PuglMod mod, uint32_t character) :
23   window (window),
24   mod (mod),
25   mod_check (true),
26   character (character)
27 {
28   leak_debugger.add (this);
29 
30   window->add_shortcut (this);
31 }
32 
33 Shortcut::~Shortcut()
34 {
35   window->remove_shortcut (this);
36 
37   leak_debugger.del (this);
38 }
39 
40 
41 Shortcut::Shortcut (Window *window, uint32_t character) :
42   window (window),
43   character (character)
44 {
45   leak_debugger.add (this);
46 
47   if (character >= 0xe000)
48     {
49       /* for keys like F1 or arrows, check that mod == 0 (to allow Shift+F1) */
50       mod_check = true;
51     }
52   if (character >= uint32_t ('A') && character <= uint32_t ('Z'))
53     {
54       /* for upper case letters, we check that mod == Shift */
55       mod = PUGL_MOD_SHIFT;
56       mod_check = true;
57     }
58   if (character >= uint32_t ('a') && character <= uint32_t ('z'))
59     {
60       /* for lower case letters, we check that mod == 0 */
61       mod_check = true;
62     }
63 
64   window->add_shortcut (this);
65 }
66 
67 static uint32_t
68 a_z_normalize (uint32_t c)
69 {
70   if (c >= 'A' && c <= 'Z') /* Shift+A .. Shift+Z */
71     return c - uint32_t ('A') + uint32_t ('a');
72 
73   if (c >= 1 && c <= 26) /* Ctrl+A .. Ctrl+Z */
74     return c - 1 + uint32_t ('a');
75 
76   return c;
77 }
78 
79 bool
80 Shortcut::focus_override()
81 {
82   /* special hack to allow SPACE as shortcut, but if a textedit is active, input goes there */
83   return !mod_check && character == ' ';
84 }
85 
86 bool
87 Shortcut::key_press_event (const PuglEventKey& key_event)
88 {
89   if (key_event.filter)
90     {
91       // printf ("filt.key_event.special=%x\n", key_event.special);
92       // printf ("filt.key_event.character=%c %x\n", key_event.character, key_event.character);
93       // printf ("filt.key_event.state=%d\n", key_event.state);
94       /* multi key sequence -> ignore */
95       return false;
96     }
97   // printf ("key_event.special=%x\n", key_event.special);
98   // printf ("key_event.character=%c %x\n", key_event.character, key_event.character);
99   // printf ("key_event.state=%d\n", key_event.state);
100 
101   const uint32_t ke_character = key_event.special ? key_event.special : key_event.character;
102   if (!mod_check)
103     {
104       if (ke_character == character)
105         {
106           signal_activated();
107           return true;
108         }
109     }
110   else if (mod == key_event.state && a_z_normalize (ke_character) == a_z_normalize (character))
111     {
112       signal_activated();
113       return true;
114     }
115   return false;
116 }
117 
118 class ShortcutDebugDialog : public Dialog
119 {
120 public:
121   ShortcutDebugDialog (Window *window, const string& text) :
122     Dialog (window)
123   {
124     FixedGrid grid;
125 
126     grid.add_widget (this, 0, 0, 40, 9);
127 
128     double yoffset = 1;
129 
130     auto web_label = new Label (this, "Keyboard Shortcut:" + text);
131 
132     grid.add_widget (web_label, 10, yoffset, 40, 3);
133     yoffset += 3;
134 
135     auto ok_button = new Button (this, "Ok");
136     grid.add_widget (ok_button, 15, yoffset, 10, 3);
137     connect (ok_button->signal_clicked, this, &Dialog::on_accept);
138 
139     window->set_keyboard_focus (this);
140   }
141 
142   void
143   key_press_event (const PuglEventKey& key_event) override
144   {
145     on_accept();
146   }
147   void
148   mouse_press (const MouseEvent& event) override
149   {
150     on_accept();
151   }
152 };
153 
154 
155 void
156 Shortcut::test (Window *window)
157 {
158   static bool dialog_visible = false;
159   auto gen_shortcut = [&] (const string& text, uint32_t ch, PuglMod mod = PuglMod (0)) {
160     Shortcut *shortcut = mod ? new Shortcut (window, mod, ch) : new Shortcut (window, ch);
161     window->connect (shortcut->signal_activated, [=] ()
162         {
163           if (!dialog_visible)
164             {
165               dialog_visible = true;
166 
167               auto dialog = new ShortcutDebugDialog (window, text.c_str());
168               dialog->run ([] (bool) { dialog_visible = false; });
169             }
170         }
171     );
172   };
173   for (int ch = 32; ch < 127; ch++)
174     gen_shortcut (string_printf ("'%c'", ch), ch);
175 
176   for (uint32_t ch = uint32_t ('a'); ch <= uint32_t ('z'); ch++)
177     {
178       gen_shortcut (string_printf ("Ctrl+%c", ch), ch, PUGL_MOD_CTRL);
179       gen_shortcut (string_printf ("Alt+%c", ch), ch, PUGL_MOD_ALT);
180       gen_shortcut (string_printf ("Super+%c", ch), ch, PUGL_MOD_SUPER);
181     }
182   for (uint32_t i = 1; i <= 12; i++)
183     {
184       gen_shortcut (string_printf ("F%d", i), PUGL_KEY_F1 + i - 1);
185       gen_shortcut (string_printf ("Shift+F%d", i), PUGL_KEY_F1 + i - 1, PUGL_MOD_SHIFT);
186       gen_shortcut (string_printf ("Super+F%d", i), PUGL_KEY_F1 + i - 1, PUGL_MOD_SUPER);
187     }
188 
189   struct KeyName { uint32_t key; string name; };
190   std::vector<KeyName> keys = {
191     { PUGL_KEY_LEFT, "Left" },
192     { PUGL_KEY_UP, "Up" },
193     { PUGL_KEY_RIGHT, "Right" },
194     { PUGL_KEY_DOWN, "Down" },
195     { PUGL_KEY_PAGE_UP, "Page Up" },
196     { PUGL_KEY_PAGE_DOWN, "Page Down" },
197     { PUGL_KEY_HOME, "Home" },
198     { PUGL_KEY_END, "End" },
199     { PUGL_KEY_INSERT, "Insert" },
200   };
201 
202   for (auto k : keys)
203     {
204       gen_shortcut (k.name, k.key);
205       gen_shortcut ("Shift+" + k.name, k.key, PUGL_MOD_SHIFT);
206       gen_shortcut ("Alt+" + k.name, k.key, PUGL_MOD_ALT);
207       gen_shortcut ("Ctrl+" + k.name, k.key, PUGL_MOD_CTRL);
208       gen_shortcut ("Super+" + k.name, k.key, PUGL_MOD_SUPER);
209     }
210 }
211