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