summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-11-21 15:07:49 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-11-21 15:07:49 +0200
commita6314e152b2d2f306bcbb880356d3890efbfc89e (patch)
tree574e5a78d63a5149213c0f9d68f4591af1e17e1f /src/ui
parent6d24d800df86e11c5686d50437932a711af82915 (diff)
Keyboard navigation mode for home row keys
Now there are keybindings for activating the keyboard navigation modes. The modifier-based mode remains as it was before, focusing on numbers, while the home row mode uses a separate activation key. One can erase the bindings to disable the corresponding modes. IssueID #34
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/bindingswidget.c6
-rw-r--r--src/ui/documentwidget.c158
-rw-r--r--src/ui/keys.c38
-rw-r--r--src/ui/util.c8
-rw-r--r--src/ui/util.h1
5 files changed, 125 insertions, 86 deletions
diff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c
index ff68ea7b..dee844db 100644
--- a/src/ui/bindingswidget.c
+++ b/src/ui/bindingswidget.c
@@ -195,6 +195,12 @@ static iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *e
195 postCommand_App("bindings.changed"); 195 postCommand_App("bindings.changed");
196 return iTrue; 196 return iTrue;
197 } 197 }
198 else if (ev->type == SDL_KEYUP && isMod_Sym(ev->key.keysym.sym)) {
199 setKey_BindingItem_(item_ListWidget(d->list, d->activePos), ev->key.keysym.sym, 0);
200 setActiveItem_BindingsWidget_(d, iInvalidPos);
201 postCommand_App("bindings.changed");
202 return iTrue;
203 }
198 } 204 }
199 return processEvent_Widget(w, ev); 205 return processEvent_Widget(w, ev);
200} 206}
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index b1c166aa..3cf564ac 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -125,11 +125,17 @@ enum iDocumentWidgetFlag {
125 showLinkNumbers_DocumentWidgetFlag = iBit(3), 125 showLinkNumbers_DocumentWidgetFlag = iBit(3),
126}; 126};
127 127
128enum iDocumentLinkOrdinalMode {
129 numbersAndAlphabet_DocumentLinkOrdinalMode,
130 homeRow_DocumentLinkOrdinalMode,
131};
132
128struct Impl_DocumentWidget { 133struct Impl_DocumentWidget {
129 iWidget widget; 134 iWidget widget;
130 enum iRequestState state; 135 enum iRequestState state;
131 iPersistentDocumentState mod; 136 iPersistentDocumentState mod;
132 int flags; 137 int flags;
138 enum iDocumentLinkOrdinalMode ordinalMode;
133 iString * titleUser; 139 iString * titleUser;
134 iGmRequest * request; 140 iGmRequest * request;
135 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ 141 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */
@@ -228,7 +234,6 @@ void init_DocumentWidget(iDocumentWidget *d) {
228 addAction_Widget(w, navigateForward_KeyShortcut, "navigate.forward"); 234 addAction_Widget(w, navigateForward_KeyShortcut, "navigate.forward");
229 addAction_Widget(w, navigateParent_KeyShortcut, "navigate.parent"); 235 addAction_Widget(w, navigateParent_KeyShortcut, "navigate.parent");
230 addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root"); 236 addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root");
231 addAction_Widget(w, 'f', 0, "document.linkkeys");
232} 237}
233 238
234void deinit_DocumentWidget(iDocumentWidget *d) { 239void deinit_DocumentWidget(iDocumentWidget *d) {
@@ -371,6 +376,15 @@ static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) {
371 } 376 }
372} 377}
373 378
379static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) {
380 iConstForEach(PtrArray, i, &d->visibleLinks) {
381 const iGmRun *run = i.ptr;
382 if (run->linkId) {
383 insert_PtrSet(d->invalidRuns, run);
384 }
385 }
386}
387
374static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { 388static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
375 const iWidget *w = constAs_Widget(d); 389 const iWidget *w = constAs_Widget(d);
376 const iRect docBounds = documentBounds_DocumentWidget_(d); 390 const iRect docBounds = documentBounds_DocumentWidget_(d);
@@ -969,7 +983,7 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur
969 /* Get rid of link numbers when scrolling. */ 983 /* Get rid of link numbers when scrolling. */
970 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { 984 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) {
971 d->flags &= ~showLinkNumbers_DocumentWidgetFlag; 985 d->flags &= ~showLinkNumbers_DocumentWidgetFlag;
972 invalidate_DocumentWidget_(d); 986 invalidateVisibleLinks_DocumentWidget_(d);
973 } 987 }
974 if (!prefs_App()->smoothScrolling) { 988 if (!prefs_App()->smoothScrolling) {
975 duration = 0; /* always instant */ 989 duration = 0; /* always instant */
@@ -1542,8 +1556,14 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1542 return iTrue; 1556 return iTrue;
1543 } 1557 }
1544 else if (equal_Command(cmd, "document.linkkeys") && document_App() == d) { 1558 else if (equal_Command(cmd, "document.linkkeys") && document_App() == d) {
1545 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue); 1559 if (argLabel_Command(cmd, "release")) {
1546 invalidate_DocumentWidget_(d); 1560 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
1561 }
1562 else {
1563 d->ordinalMode = arg_Command(cmd);
1564 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);
1565 }
1566 invalidateVisibleLinks_DocumentWidget_(d);
1547 refresh_Widget(d); 1567 refresh_Widget(d);
1548 return iTrue; 1568 return iTrue;
1549 } 1569 }
@@ -1815,45 +1835,75 @@ static iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_E
1815 return iFalse; 1835 return iFalse;
1816} 1836}
1817 1837
1818static size_t linkOrdinalFromKey_(int key) { 1838/* Sorted by proximity to F and J. */
1819 if (key >= '1' && key <= '9') { 1839static const int homeRowKeys_[] = {
1820 return key - '1'; 1840 'f', 'd', 's', 'a',
1821 } 1841 'j', 'k', 'l',
1822 if (key < 'a' || key > 'z') { 1842 'r', 'e', 'w', 'q',
1823 return iInvalidPos; 1843 'u', 'i', 'o', 'p',
1824 } 1844 'v', 'c', 'x', 'z',
1825 int ord = key - 'a' + 9; 1845 'm', 'n',
1846 'g', 'h',
1847 'b',
1848 't', 'y', 'u',
1849};
1850
1851static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int key) {
1852 size_t ord = iInvalidPos;
1853 if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) {
1854 if (key >= '1' && key <= '9') {
1855 return key - '1';
1856 }
1857 if (key < 'a' || key > 'z') {
1858 return iInvalidPos;
1859 }
1860 ord = key - 'a' + 9;
1826#if defined (iPlatformApple) 1861#if defined (iPlatformApple)
1827 /* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */ 1862 /* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */
1828 if (key == 'h' || key == 'm' || key == 'q' || key == 'w') { 1863 if (key == 'h' || key == 'm' || key == 'q' || key == 'w') {
1829 return iInvalidPos; 1864 return iInvalidPos;
1830 } 1865 }
1831 if (key > 'h') ord--; 1866 if (key > 'h') ord--;
1832 if (key > 'm') ord--; 1867 if (key > 'm') ord--;
1833 if (key > 'q') ord--; 1868 if (key > 'q') ord--;
1834 if (key > 'w') ord--; 1869 if (key > 'w') ord--;
1835#endif 1870#endif
1871 }
1872 else {
1873 iForIndices(i, homeRowKeys_) {
1874 if (homeRowKeys_[i] == key) {
1875 return i;
1876 }
1877 }
1878 }
1836 return ord; 1879 return ord;
1837} 1880}
1838 1881
1839static iChar linkOrdinalChar_(size_t ord) { 1882static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) {
1840 if (ord < 9) { 1883 if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) {
1841 return 0x278a + ord; 1884 if (ord < 9) {
1842 } 1885 return 0x278a + ord;
1886 }
1843#if defined (iPlatformApple) 1887#if defined (iPlatformApple)
1844 if (ord < 9 + 22) { 1888 if (ord < 9 + 22) {
1845 int key = 'a' + ord - 9; 1889 int key = 'a' + ord - 9;
1846 if (key >= 'h') key++; 1890 if (key >= 'h') key++;
1847 if (key >= 'm') key++; 1891 if (key >= 'm') key++;
1848 if (key >= 'q') key++; 1892 if (key >= 'q') key++;
1849 if (key >= 'w') key++; 1893 if (key >= 'w') key++;
1850 return 0x24b6 + key - 'a'; 1894 return 0x24b6 + key - 'a';
1851 } 1895 }
1852#else 1896#else
1853 if (ord < 9 + 26) { 1897 if (ord < 9 + 26) {
1854 return 0x24b6 + ord - 9; 1898 return 0x24b6 + ord - 9;
1855 } 1899 }
1856#endif 1900#endif
1901 }
1902 else {
1903 if (ord < iElemCount(homeRowKeys_)) {
1904 return 0x24b6 + homeRowKeys_[ord] - 'a';
1905 }
1906 }
1857 return 0; 1907 return 0;
1858} 1908}
1859 1909
@@ -1866,31 +1916,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1866 } 1916 }
1867 return iTrue; 1917 return iTrue;
1868 } 1918 }
1869 if (ev->type == SDL_KEYUP) {
1870 const int key = ev->key.keysym.sym;
1871 switch (key) {
1872 case SDLK_LALT:
1873 case SDLK_RALT:
1874 if (document_App() == d) {
1875 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
1876 invalidate_DocumentWidget_(d);
1877 refresh_Widget(w);
1878 }
1879 break;
1880 case SDLK_PAGEUP:
1881 case SDLK_PAGEDOWN:
1882 case SDLK_SPACE:
1883 case SDLK_UP:
1884 case SDLK_DOWN:
1885// d->smoothContinue = iFalse;
1886 break;
1887 }
1888 }
1889 if (ev->type == SDL_KEYDOWN) { 1919 if (ev->type == SDL_KEYDOWN) {
1890 const int key = ev->key.keysym.sym; 1920 const int key = ev->key.keysym.sym;
1891 if ((d->flags & showLinkNumbers_DocumentWidgetFlag) && 1921 if ((d->flags & showLinkNumbers_DocumentWidgetFlag) &&
1892 ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) { 1922 ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) {
1893 const size_t ord = linkOrdinalFromKey_(key); 1923 const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key);
1894 iConstForEach(PtrArray, i, &d->visibleLinks) { 1924 iConstForEach(PtrArray, i, &d->visibleLinks) {
1895 if (ord == iInvalidPos) break; 1925 if (ord == iInvalidPos) break;
1896 const iGmRun *run = i.ptr; 1926 const iGmRun *run = i.ptr;
@@ -1904,6 +1934,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1904 cstr_String(absoluteUrl_String( 1934 cstr_String(absoluteUrl_String(
1905 d->mod.url, linkUrl_GmDocument(d->doc, run->linkId)))); 1935 d->mod.url, linkUrl_GmDocument(d->doc, run->linkId))));
1906 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); 1936 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
1937 invalidateVisibleLinks_DocumentWidget_(d);
1938 refresh_Widget(d);
1907 return iTrue; 1939 return iTrue;
1908 } 1940 }
1909 } 1941 }
@@ -1912,19 +1944,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1912 case SDLK_ESCAPE: 1944 case SDLK_ESCAPE:
1913 if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) { 1945 if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) {
1914 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); 1946 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
1915 invalidate_DocumentWidget_(d); 1947 invalidateVisibleLinks_DocumentWidget_(d);
1916 refresh_Widget(d); 1948 refresh_Widget(d);
1917 return iTrue; 1949 return iTrue;
1918 } 1950 }
1919 break; 1951 break;
1920 case SDLK_LALT:
1921 case SDLK_RALT:
1922 if (document_App() == d) {
1923 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);
1924 invalidate_DocumentWidget_(d);
1925 refresh_Widget(w);
1926 }
1927 break;
1928#if 1 1952#if 1
1929 case SDLK_KP_1: 1953 case SDLK_KP_1:
1930 case '`': { 1954 case '`': {
@@ -2374,11 +2398,11 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
2374 else { 2398 else {
2375 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { 2399 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) {
2376 const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId); 2400 const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId);
2377 const iChar ordChar = linkOrdinalChar_(ord); 2401 const iChar ordChar = linkOrdinalChar_DocumentWidget_(d->widget, ord);
2378 if (ordChar) { 2402 if (ordChar) {
2379 drawString_Text(run->font, 2403 drawString_Text(run->font,
2380 init_I2(d->viewPos.x - gap_UI / 3, visPos.y), 2404 init_I2(d->viewPos.x - gap_UI / 3, visPos.y),
2381 fg, 2405 tmQuote_ColorId,
2382 collect_String(newUnicodeN_String(&ordChar, 1))); 2406 collect_String(newUnicodeN_String(&ordChar, 1)));
2383 goto runDrawn; 2407 goto runDrawn;
2384 } 2408 }
diff --git a/src/ui/keys.c b/src/ui/keys.c
index ea874343..d42ecfba 100644
--- a/src/ui/keys.c
+++ b/src/ui/keys.c
@@ -57,7 +57,8 @@ static void clear_Keys_(iKeys *d) {
57} 57}
58 58
59enum iBindFlag { 59enum iBindFlag {
60 argRepeat_BindFlag = iBit(1), 60 argRepeat_BindFlag = iBit(1),
61 argRelease_BindFlag = iBit(2),
61}; 62};
62 63
63/* TODO: This indirection could be used for localization, although all UI strings 64/* TODO: This indirection could be used for localization, although all UI strings
@@ -73,7 +74,8 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] =
73 { 31, { "Go forward", navigateForward_KeyShortcut, "navigate.forward" }, 0 }, 74 { 31, { "Go forward", navigateForward_KeyShortcut, "navigate.forward" }, 0 },
74 { 32, { "Go to parent directory", navigateParent_KeyShortcut, "navigate.parent" }, 0 }, 75 { 32, { "Go to parent directory", navigateParent_KeyShortcut, "navigate.parent" }, 0 },
75 { 33, { "Go to site root", navigateRoot_KeyShortcut, "navigate.root" }, 0 }, 76 { 33, { "Go to site root", navigateRoot_KeyShortcut, "navigate.root" }, 0 },
76 { 40, { "Open link via keyboard", 'f', 0, "document.linkkeys" }, 0 }, 77 { 40, { "Open link via home row keys", 'f', 0, "document.linkkeys arg:1" }, 0 },
78 { 41, { "Open link via modifier key", SDLK_LALT, 0, "document.linkkeys arg:0" }, argRelease_BindFlag },
77 /* The following cannot currently be changed (built-in duplicates). */ 79 /* The following cannot currently be changed (built-in duplicates). */
78 { 1000, { NULL, SDLK_SPACE, KMOD_SHIFT, "scroll.page arg:-1" }, argRepeat_BindFlag }, 80 { 1000, { NULL, SDLK_SPACE, KMOD_SHIFT, "scroll.page arg:-1" }, argRepeat_BindFlag },
79 { 1001, { NULL, SDLK_SPACE, 0, "scroll.page arg:1" }, argRepeat_BindFlag }, 81 { 1001, { NULL, SDLK_SPACE, 0, "scroll.page arg:1" }, argRepeat_BindFlag },
@@ -110,6 +112,11 @@ static void bindDefaults_(void) {
110 112
111static iBinding *find_Keys_(iKeys *d, int key, int mods) { 113static iBinding *find_Keys_(iKeys *d, int key, int mods) {
112 size_t pos; 114 size_t pos;
115 /* Do not differentiate between left and right modifier keys. */
116 key = normalizedMod_Sym(key);
117 if (isMod_Sym(key)) {
118 mods = 0;
119 }
113 const iBinding elem = { .key = key, .mods = mods }; 120 const iBinding elem = { .key = key, .mods = mods };
114 if (locate_PtrSet(&d->lookup, &elem, &pos)) { 121 if (locate_PtrSet(&d->lookup, &elem, &pos)) {
115 return at_PtrSet(&d->lookup, pos); 122 return at_PtrSet(&d->lookup, pos);
@@ -138,8 +145,8 @@ static void updateLookup_Keys_(iKeys *d) {
138void setKey_Binding(int id, int key, int mods) { 145void setKey_Binding(int id, int key, int mods) {
139 iBinding *bind = findId_Keys_(&keys_, id); 146 iBinding *bind = findId_Keys_(&keys_, id);
140 if (bind) { 147 if (bind) {
141 bind->key = key; 148 bind->key = normalizedMod_Sym(key);
142 bind->mods = mods; 149 bind->mods = isMod_Sym(key) ? 0 : mods;
143 updateLookup_Keys_(&keys_); 150 updateLookup_Keys_(&keys_);
144 } 151 }
145} 152}
@@ -252,25 +259,18 @@ void setLabel_Keys(int id, const char *label) {
252 } 259 }
253} 260}
254 261
255#if 0
256const iString *label_Keys(const char *command) {
257 iKeys *d = &keys_;
258 /* TODO: A hash wouldn't hurt here. */
259 iConstForEach(PtrSet, i, &d->bindings) {
260 const iBinding *bind = *i.value;
261 if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {
262 return &bind->label;
263 }
264 }
265 return collectNew_String();
266}
267#endif
268
269iBool processEvent_Keys(const SDL_Event *ev) { 262iBool processEvent_Keys(const SDL_Event *ev) {
270 iKeys *d = &keys_; 263 iKeys *d = &keys_;
271 if (ev->type == SDL_KEYDOWN) { 264 if (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) {
272 const iBinding *bind = find_Keys_(d, ev->key.keysym.sym, keyMods_Sym(ev->key.keysym.mod)); 265 const iBinding *bind = find_Keys_(d, ev->key.keysym.sym, keyMods_Sym(ev->key.keysym.mod));
273 if (bind) { 266 if (bind) {
267 if (ev->type == SDL_KEYUP) {
268 if (bind->flags & argRelease_BindFlag) {
269 postCommandf_App("%s release:1", cstr_String(&bind->command));
270 return iTrue;
271 }
272 return iFalse;
273 }
274 if (ev->key.repeat && (bind->flags & argRepeat_BindFlag)) { 274 if (ev->key.repeat && (bind->flags & argRepeat_BindFlag)) {
275 postCommandf_App("%s repeat:1", cstr_String(&bind->command)); 275 postCommandf_App("%s repeat:1", cstr_String(&bind->command));
276 } 276 }
diff --git a/src/ui/util.c b/src/ui/util.c
index 559c5381..c1312062 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -117,6 +117,14 @@ iBool isMod_Sym(int key) {
117 key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT; 117 key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;
118} 118}
119 119
120int normalizedMod_Sym(int key) {
121 if (key == SDLK_RSHIFT) key = SDLK_LSHIFT;
122 if (key == SDLK_RCTRL) key = SDLK_LCTRL;
123 if (key == SDLK_RALT) key = SDLK_LALT;
124 if (key == SDLK_RGUI) key = SDLK_LGUI;
125 return key;
126}
127
120int keyMods_Sym(int kmods) { 128int keyMods_Sym(int kmods) {
121 kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI); 129 kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);
122 /* Don't treat left/right modifiers differently. */ 130 /* Don't treat left/right modifiers differently. */
diff --git a/src/ui/util.h b/src/ui/util.h
index c0e3a04c..f7a67f9a 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -49,6 +49,7 @@ iLocalDef iBool isResize_UserEvent(const SDL_Event *d) {
49#endif 49#endif
50 50
51iBool isMod_Sym (int key); 51iBool isMod_Sym (int key);
52int normalizedMod_Sym (int key);
52int keyMods_Sym (int kmods); /* shift, alt, control, or gui */ 53int keyMods_Sym (int kmods); /* shift, alt, control, or gui */
53void toString_Sym (int key, int kmods, iString *str); 54void toString_Sym (int key, int kmods, iString *str);
54 55