From a6314e152b2d2f306bcbb880356d3890efbfc89e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaakko=20Ker=C3=A4nen?= jaakko.keranen@iki.fi
Date: Sat, 21 Nov 2020 15:07:49 +0200
Subject: [PATCH 1/1] 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
res/about/version.gmi | 5 +-
src/ui/bindingswidget.c | 6 ++
src/ui/documentwidget.c | 158 +++++++++++++++++++++++-----------------
src/ui/keys.c | 38 +++++-----
src/ui/util.c | 8 ++
src/ui/util.h | 1 +
6 files changed, 128 insertions(+), 88 deletions(-)
diff --git a/res/about/version.gmi b/res/about/version.gmi
index 1b8e502a..48b4cbd1 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -7,9 +7,10 @@
-* Added option to load inline images when pressing Space or ↓ for a more focused reading experience — just keep tapping on a single key to proceed. If an image link is visible, it will be loaded instead of scrolling. This option is disabled by default.
+* Added option to load inline images when pressing Space or ↓ for a more focused reading experience — just keep tapping a single key to proceed. If an image link is visible, it will be loaded instead of scrolling. This option is disabled by default.
-* Added a keybinding to activate keyboard link navigation mode (default is "F").
+* Added a new keyboard link navigation mode focusing on the home row keys. The default keybinding for this is "F".
+* Added a keybinding to activate keyboard link modifier mode. The keyboard link keys are active while the modifier is held down. The default is ${ALT}.
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
postCommand_App("bindings.changed");
return iTrue;
}
else if (ev->type == SDL_KEYUP && isMod_Sym(ev->key.keysym.sym)) {
setKey_BindingItem_(item_ListWidget(d->list, d->activePos), ev->key.keysym.sym, 0);
setActiveItem_BindingsWidget_(d, iInvalidPos);
postCommand_App("bindings.changed");
return iTrue;
}
}
return processEvent_Widget(w, ev);
}
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 {
showLinkNumbers_DocumentWidgetFlag = iBit(3),
};
+enum iDocumentLinkOrdinalMode {
+};
struct Impl_DocumentWidget {
iWidget widget;
enum iRequestState state;
iPersistentDocumentState mod;
int flags;
iString * titleUser;
iGmRequest * request;
iAtomicInt isRequestUpdated; /* request has new content, need to parse it */
@@ -228,7 +234,6 @@ void init_DocumentWidget(iDocumentWidget *d) {
addAction_Widget(w, navigateForward_KeyShortcut, "navigate.forward");
addAction_Widget(w, navigateParent_KeyShortcut, "navigate.parent");
addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root");
}
void deinit_DocumentWidget(iDocumentWidget *d) {
@@ -371,6 +376,15 @@ static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) {
}
}
+static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) {
const iGmRun *run = i.ptr;
if (run->linkId) {
insert_PtrSet(d->invalidRuns, run);
}
+}
static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
const iWidget *w = constAs_Widget(d);
const iRect docBounds = documentBounds_DocumentWidget_(d);
@@ -969,7 +983,7 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur
/* Get rid of link numbers when scrolling. */
if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) {
d->flags &= ~showLinkNumbers_DocumentWidgetFlag;
invalidate_DocumentWidget_(d);
invalidateVisibleLinks_DocumentWidget_(d);
}
if (!prefs_App()->smoothScrolling) {
duration = 0; /* always instant */
@@ -1542,8 +1556,14 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
return iTrue;
}
else if (equal_Command(cmd, "document.linkkeys") && document_App() == d) {
iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);
invalidate_DocumentWidget_(d);
if (argLabel_Command(cmd, "release")) {
iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
}
else {
d->ordinalMode = arg_Command(cmd);
iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);
}
invalidateVisibleLinks_DocumentWidget_(d);
refresh_Widget(d);
return iTrue;
}
@@ -1815,45 +1835,75 @@ static iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_E
return iFalse;
}
-static size_t linkOrdinalFromKey_(int key) {
return key - '1';
return iInvalidPos;
+/* Sorted by proximity to F and J. */
+static const int homeRowKeys_[] = {
+};
+static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int key) {
if (key >= '1' && key <= '9') {
return key - '1';
}
if (key < 'a' || key > 'z') {
return iInvalidPos;
}
ord = key - 'a' + 9;
#if defined (iPlatformApple)
return iInvalidPos;
/* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */
if (key == 'h' || key == 'm' || key == 'q' || key == 'w') {
return iInvalidPos;
}
if (key > 'h') ord--;
if (key > 'm') ord--;
if (key > 'q') ord--;
if (key > 'w') ord--;
#endif
iForIndices(i, homeRowKeys_) {
if (homeRowKeys_[i] == key) {
return i;
}
}
return ord;
}
-static iChar linkOrdinalChar_(size_t ord) {
return 0x278a + ord;
+static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) {
if (ord < 9) {
return 0x278a + ord;
}
#if defined (iPlatformApple)
int key = 'a' + ord - 9;
if (key >= 'h') key++;
if (key >= 'm') key++;
if (key >= 'q') key++;
if (key >= 'w') key++;
return 0x24b6 + key - 'a';
if (ord < 9 + 22) {
int key = 'a' + ord - 9;
if (key >= 'h') key++;
if (key >= 'm') key++;
if (key >= 'q') key++;
if (key >= 'w') key++;
return 0x24b6 + key - 'a';
}
#else
return 0x24b6 + ord - 9;
if (ord < 9 + 26) {
return 0x24b6 + ord - 9;
}
#endif
if (ord < iElemCount(homeRowKeys_)) {
return 0x24b6 + homeRowKeys_[ord] - 'a';
}
return 0;
}
@@ -1866,31 +1916,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
}
return iTrue;
}
const int key = ev->key.keysym.sym;
switch (key) {
case SDLK_LALT:
case SDLK_RALT:
if (document_App() == d) {
iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
invalidate_DocumentWidget_(d);
refresh_Widget(w);
}
break;
case SDLK_PAGEUP:
case SDLK_PAGEDOWN:
case SDLK_SPACE:
case SDLK_UP:
case SDLK_DOWN:
-// d->smoothContinue = iFalse;
break;
}
if (ev->type == SDL_KEYDOWN) {
const int key = ev->key.keysym.sym;
if ((d->flags & showLinkNumbers_DocumentWidgetFlag) &&
((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) {
const size_t ord = linkOrdinalFromKey_(key);
const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key);
iConstForEach(PtrArray, i, &d->visibleLinks) {
if (ord == iInvalidPos) break;
const iGmRun *run = i.ptr;
@@ -1904,6 +1934,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
cstr_String(absoluteUrl_String(
d->mod.url, linkUrl_GmDocument(d->doc, run->linkId))));
iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
invalidateVisibleLinks_DocumentWidget_(d);
refresh_Widget(d);
return iTrue;
}
}
@@ -1912,19 +1944,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
case SDLK_ESCAPE:
if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) {
iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
invalidate_DocumentWidget_(d);
invalidateVisibleLinks_DocumentWidget_(d);
refresh_Widget(d);
return iTrue;
}
break;
case SDLK_LALT:
case SDLK_RALT:
if (document_App() == d) {
iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);
invalidate_DocumentWidget_(d);
refresh_Widget(w);
}
break;
#if 1
case SDLK_KP_1:
case '`': {
@@ -2374,11 +2398,11 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
else {
if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) {
const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId);
const iChar ordChar = linkOrdinalChar_(ord);
const iChar ordChar = linkOrdinalChar_DocumentWidget_(d->widget, ord);
if (ordChar) {
drawString_Text(run->font,
init_I2(d->viewPos.x - gap_UI / 3, visPos.y),
fg,
tmQuote_ColorId,
collect_String(newUnicodeN_String(&ordChar, 1)));
goto runDrawn;
}
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) {
}
enum iBindFlag {
};
/* 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_[] =
{ 31, { "Go forward", navigateForward_KeyShortcut, "navigate.forward" }, 0 },
{ 32, { "Go to parent directory", navigateParent_KeyShortcut, "navigate.parent" }, 0 },
{ 33, { "Go to site root", navigateRoot_KeyShortcut, "navigate.root" }, 0 },
/* The following cannot currently be changed (built-in duplicates). */
{ 1000, { NULL, SDLK_SPACE, KMOD_SHIFT, "scroll.page arg:-1" }, argRepeat_BindFlag },
{ 1001, { NULL, SDLK_SPACE, 0, "scroll.page arg:1" }, argRepeat_BindFlag },
@@ -110,6 +112,11 @@ static void bindDefaults_(void) {
static iBinding *find_Keys_(iKeys *d, int key, int mods) {
size_t pos;
mods = 0;
const iBinding elem = { .key = key, .mods = mods };
if (locate_PtrSet(&d->lookup, &elem, &pos)) {
return at_PtrSet(&d->lookup, pos);
@@ -138,8 +145,8 @@ static void updateLookup_Keys_(iKeys *d) {
void setKey_Binding(int id, int key, int mods) {
iBinding *bind = findId_Keys_(&keys_, id);
if (bind) {
bind->key = key;
bind->mods = mods;
bind->key = normalizedMod_Sym(key);
bind->mods = isMod_Sym(key) ? 0 : mods;
updateLookup_Keys_(&keys_);
}
}
@@ -252,25 +259,18 @@ void setLabel_Keys(int id, const char *label) {
}
}
-#if 0
-const iString *label_Keys(const char *command) {
const iBinding *bind = *i.value;
if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {
return &bind->label;
}
-}
-#endif
iBool processEvent_Keys(const SDL_Event *ev) {
iKeys *d = &keys_;
const iBinding *bind = find_Keys_(d, ev->key.keysym.sym, keyMods_Sym(ev->key.keysym.mod));
if (bind) {
if (ev->type == SDL_KEYUP) {
if (bind->flags & argRelease_BindFlag) {
postCommandf_App("%s release:1", cstr_String(&bind->command));
return iTrue;
}
return iFalse;
}
if (ev->key.repeat && (bind->flags & argRepeat_BindFlag)) {
postCommandf_App("%s repeat:1", cstr_String(&bind->command));
}
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) {
key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;
}
+int normalizedMod_Sym(int key) {
+}
int keyMods_Sym(int kmods) {
kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);
/* 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) {
#endif
iBool isMod_Sym (int key);
+int normalizedMod_Sym (int key);
int keyMods_Sym (int kmods); /* shift, alt, control, or gui */
void toString_Sym (int key, int kmods, iString *str);
--
2.25.1
text/plain
This content has been proxied by September (ba2dc).