diff --git a/CMakeLists.txt b/CMakeLists.txt

index 7d73956d..cfd42044 100644

--- a/CMakeLists.txt

+++ b/CMakeLists.txt

@@ -123,6 +123,8 @@ set (SOURCES

 src/audio/player.h

 src/audio/stb_vorbis.c

 # User interface:

+ src/ui/bindingswidget.c

+ src/ui/bindingswidget.h

 src/ui/color.c

 src/ui/color.h

 src/ui/command.c

diff --git a/src/app.c b/src/app.c

index 10f5f0d2..311a88bb 100644

--- a/src/app.c

+++ b/src/app.c

@@ -430,6 +430,7 @@ static void init_App_(iApp *d, int argc, char **argv) {

static void deinit_App(iApp *d) {

 saveState_App_(d);

 save_Keys(dataDir_App_);

+ deinit_Keys();

 savePrefs_App_(d);

 deinit_Prefs(&d->prefs);

 save_Bookmarks(d->bookmarks, dataDir_App_);

diff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c

new file mode 100644

index 00000000..4ce6ea4d

--- /dev/null

+++ b/src/ui/bindingswidget.c

@@ -0,0 +1,193 @@

+/* Copyright 2020 Jaakko Keränen jaakko.keranen@iki.fi

+

+Redistribution and use in source and binary forms, with or without

+modification, are permitted provided that the following conditions are met:

+

+1. Redistributions of source code must retain the above copyright notice, this

+ list of conditions and the following disclaimer.

+2. Redistributions in binary form must reproduce the above copyright notice,

+ this list of conditions and the following disclaimer in the documentation

+ and/or other materials provided with the distribution.

+

+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND

+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE

+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR

+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES

+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;

+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON

+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT

+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

+

+#include "bindingswidget.h"

+#include "listwidget.h"

+#include "keys.h"

+#include "command.h"

+#include "util.h"

+#include "app.h"

+

+iDeclareType(BindingItem)

+typedef iListItemClass iBindingItemClass;

+

+struct Impl_BindingItem {

+ iListItem listItem;

+ iString label;

+ iString key;

+ int id;

+ iBool isWaitingForEvent;

+};

+

+void init_BindingItem(iBindingItem *d) {

+ init_ListItem(&d->listItem);

+ init_String(&d->label);

+ init_String(&d->key);

+ d->id = 0;

+ d->isWaitingForEvent = iFalse;

+}

+

+void deinit_BindingItem(iBindingItem *d) {

+ deinit_String(&d->key);

+ deinit_String(&d->label);

+}

+

+static void setKey_BindingItem_(iBindingItem *d, int key, int mods) {

+ setKey_Binding(d->id, key, mods);

+ clear_String(&d->key);

+ toString_Sym(key, mods, &d->key);

+}

+

+static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,

+ const iListWidget *list);

+

+iBeginDefineSubclass(BindingItem, ListItem)

+ .draw = (iAny *) draw_BindingItem_,

+iEndDefineSubclass(BindingItem)

+

+iDefineObjectConstruction(BindingItem)

+

+/----------------------------------------------------------------------------------------------/

+

+struct Impl_BindingsWidget {

+ iWidget widget;

+ iListWidget *list;

+ size_t activePos;

+};

+

+iDefineObjectConstruction(BindingsWidget)

+

+static int cmpId_BindingItem_(const iListItem **item1, const iListItem **item2) {

+ const iBindingItem *d = (const iBindingItem *) *item1, *other = (const iBindingItem *) *item2;

+ return iCmp(d->id, other->id);

+}

+

+static void updateItems_BindingsWidget_(iBindingsWidget *d) {

+ clear_ListWidget(d->list);

+ iConstForEach(PtrArray, i, list_Keys()) {

+ const iBinding *bind = i.ptr;

+ if (isEmpty_String(&bind->label)) {

+ /* Only the ones with label are user-changeable. */

+ continue;

+ }

+ iBindingItem *item = new_BindingItem();

+ item->id = bind->id;

+ set_String(&item->label, &bind->label);

+ toString_Sym(bind->key, bind->mods, &item->key);

+ addItem_ListWidget(d->list, item);

+ }

+ sort_ListWidget(d->list, cmpId_BindingItem_);

+ updateVisible_ListWidget(d->list);

+ invalidate_ListWidget(d->list);

+}

+

+void init_BindingsWidget(iBindingsWidget *d) {

+ iWidget *w = as_Widget(d);

+ init_Widget(w);

+ setFlags_Widget(w, resizeChildren_WidgetFlag, iTrue);

+ d->activePos = iInvalidPos;

+ d->list = new_ListWidget();

+ setItemHeight_ListWidget(d->list, lineHeight_Text(uiLabel_FontId) * 1.5f);

+ addChild_Widget(w, iClob(d->list));

+ updateItems_BindingsWidget_(d);

+}

+

+void deinit_BindingsWidget(iBindingsWidget *d) {

+ /* nothing to do */

+ iUnused(d);

+}

+

+static void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) {

+ if (d->activePos != iInvalidPos) {

+ iBindingItem *item = item_ListWidget(d->list, d->activePos);

+ item->isWaitingForEvent = iFalse;

+ invalidateItem_ListWidget(d->list, d->activePos);

+ }

+ d->activePos = pos;

+ if (d->activePos != iInvalidPos) {

+ iBindingItem *item = item_ListWidget(d->list, d->activePos);

+ item->isWaitingForEvent = iTrue;

+ invalidateItem_ListWidget(d->list, d->activePos);

+ }

+}

+

+static iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *ev) {

+ iWidget * w = as_Widget(d);

+ const char *cmd = command_UserEvent(ev);

+ if (isCommand_Widget(w, ev, "list.clicked")) {

+ setActiveItem_BindingsWidget_(d, arg_Command(cmd));

+ return iTrue;

+ }

+ /* Waiting for a keypress? */

+ if (d->activePos != iInvalidPos) {

+ if (ev->type == SDL_KEYDOWN && !isMod_Sym(ev->key.keysym.sym)) {

+ setKey_BindingItem_(item_ListWidget(d->list, d->activePos),

+ ev->key.keysym.sym,

+ keyMods_Sym(ev->key.keysym.mod));

+ setActiveItem_BindingsWidget_(d, iInvalidPos);

+ postCommand_App("bindings.changed");

+ return iTrue;

+ }

+ }

+ return processEvent_Widget(w, ev);

+}

+

+static void draw_BindingsWidget_(const iBindingsWidget *d) {

+ const iWidget *w = constAs_Widget(d);

+ drawChildren_Widget(w);

+ drawBackground_Widget(w); /* kludge to allow drawing a top border over the list */

+}

+

+static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,

+ const iListWidget *list) {

+ const int font = uiLabel_FontId;

+ const int itemHeight = height_Rect(itemRect);

+ const int line = lineHeight_Text(font);

+ int fg = uiText_ColorId;

+ const iBool isPressing = isMouseDown_ListWidget(list) || d->isWaitingForEvent;

+ const iBool isHover = (isHover_Widget(constAs_Widget(list)) &&

+ constHoverItem_ListWidget(list) == d);

+ if (isHover || isPressing) {

+ fg = isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId;

+ fillRect_Paint(p,

+ itemRect,

+ isPressing ? uiBackgroundPressed_ColorId

+ : uiBackgroundFramelessHover_ColorId);

+ }

+ const int y = top_Rect(itemRect) + (itemHeight - line) / 2;

+ drawRange_Text(font,

+ init_I2(left_Rect(itemRect) + 3 * gap_UI, y),

+ fg,

+ range_String(&d->label));

+ drawAlign_Text(d->isWaitingForEvent ? uiContent_FontId : font,

+ init_I2(right_Rect(itemRect) - 3 * gap_UI,

+ y - (lineHeight_Text(uiContent_FontId) - line) / 2),

+ fg,

+ right_Alignment,

+ "%s",

+ d->isWaitingForEvent ? "\U0001F449 \u2328" : cstr_String(&d->key));

+}

+

+iBeginDefineSubclass(BindingsWidget, Widget)

+ .processEvent = (iAny *) processEvent_BindingsWidget_,

+ .draw = (iAny *) draw_BindingsWidget_,

+iEndDefineSubclass(BindingsWidget)

diff --git a/src/ui/bindingswidget.h b/src/ui/bindingswidget.h

new file mode 100644

index 00000000..e1ed402c

--- /dev/null

+++ b/src/ui/bindingswidget.h

@@ -0,0 +1,28 @@

+/* Copyright 2020 Jaakko Keränen jaakko.keranen@iki.fi

+

+Redistribution and use in source and binary forms, with or without

+modification, are permitted provided that the following conditions are met:

+

+1. Redistributions of source code must retain the above copyright notice, this

+ list of conditions and the following disclaimer.

+2. Redistributions in binary form must reproduce the above copyright notice,

+ this list of conditions and the following disclaimer in the documentation

+ and/or other materials provided with the distribution.

+

+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND

+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE

+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR

+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES

+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;

+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON

+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT

+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

+

+#pragma once

+

+#include "widget.h"

+

+iDeclareWidgetClass(BindingsWidget)

+iDeclareObjectConstruction(BindingsWidget)

diff --git a/src/ui/keys.c b/src/ui/keys.c

index 85304ef7..cc7dabef 100644

--- a/src/ui/keys.c

+++ b/src/ui/keys.c

@@ -24,12 +24,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

#include "util.h"

#include "app.h"



-#include <the_Foundation/sortedarray.h>

+#include <the_Foundation/ptrset.h>



iDeclareType(Keys)



-static int cmp_Binding_(const void *a, const void *b) {

- const iBinding *d = a, *other = b;

+static int cmpPtr_Binding_(const void *a, const void *b) {

+ const iBinding *d = *(const void **) a, *other = *(const void **) b;

 const int cmp = iCmp(d->key, other->key);

 if (cmp == 0) {

     return iCmp(d->mods, other->mods);

@@ -37,14 +37,17 @@ static int cmp_Binding_(const void *a, const void *b) {

 return cmp;

}



+/----------------------------------------------------------------------------------------------/

+

struct Impl_Keys {

- iSortedArray bindings;

+ iArray bindings;

+ iPtrSet lookup; /* quick key/mods lookup */

};



static iKeys keys_;



static void clear_Keys_(iKeys *d) {

- iForEach(Array, i, &d->bindings.values) {

+ iForEach(Array, i, &d->bindings) {

     iBinding *bind = i.value;

     deinit_String(&bind->command);

     deinit_String(&bind->label);

@@ -52,27 +55,41 @@ static void clear_Keys_(iKeys *d) {

}



static void bindDefaults_(void) {

- bind_Keys("scroll.top", SDLK_HOME, 0);

- bind_Keys("scroll.bottom", SDLK_END, 0);

- bind_Keys("scroll.step arg:-1", SDLK_UP, 0);

- bind_Keys("scroll.step arg:1", SDLK_DOWN, 0);

- bind_Keys("scroll.page arg:-1", SDLK_PAGEUP, 0);

- bind_Keys("scroll.page arg:1", SDLK_PAGEDOWN, 0);

- bind_Keys("scroll.page arg:-1", SDLK_SPACE, KMOD_SHIFT);

- bind_Keys("scroll.page arg:1", SDLK_SPACE, 0);

+ /* TODO: This indirection could be used for localization, although all UI strings

+ would need to be similarly handled. */

+ bindLabel_Keys(1, "scroll.top", SDLK_HOME, 0, "Jump to top");

+ bindLabel_Keys(2, "scroll.bottom", SDLK_END, 0, "Jump to bottom");

+ bindLabel_Keys(10, "scroll.step arg:-1", SDLK_UP, 0, "Scroll up");

+ bindLabel_Keys(11, "scroll.step arg:1", SDLK_DOWN, 0, "Scroll down");

+ bindLabel_Keys(20, "scroll.page arg:-1", SDLK_PAGEUP, 0, "Scroll up half a page");

+ bindLabel_Keys(21, "scroll.page arg:1", SDLK_PAGEDOWN, 0, "Scroll down half a page");

+ /* The following cannot currently be changed (built-in duplicates). */

+ bind_Keys(1000, "scroll.page arg:-1", SDLK_SPACE, KMOD_SHIFT);

+ bind_Keys(1001, "scroll.page arg:1", SDLK_SPACE, 0);

}



static iBinding *find_Keys_(iKeys *d, int key, int mods) {

 size_t pos;

- if (locate_SortedArray(&d->bindings, &(iBinding){ .key = key, .mods = mods }, &pos)) {

- return at_SortedArray(&d->bindings, pos);

+ const iBinding elem = { .key = key, .mods = mods };

+ if (locate_PtrSet(&d->lookup, &elem, &pos)) {

+ return at_PtrSet(&d->lookup, pos);

+ }

+ return NULL;

+}

+

+static iBinding *findId_Keys_(iKeys *d, int id) {

+ iForEach(Array, i, &d->bindings) {

+ iBinding *bind = i.value;

+ if (bind->id == id) {

+ return bind;

+ }

 }

 return NULL;

}



static iBinding *findCommand_Keys_(iKeys *d, const char *command) {

 /* Note: O(n) */

- iForEach(Array, i, &d->bindings.values) {

+ iForEach(Array, i, &d->bindings) {

     iBinding *bind = i.value;

     if (!cmp_String(&bind->command, command)) {

         return bind;

@@ -81,18 +98,37 @@ static iBinding *findCommand_Keys_(iKeys *d, const char *command) {

 return NULL;

}



+static void updateLookup_Keys_(iKeys *d) {

+ clear_PtrSet(&d->lookup);

+ iConstForEach(Array, i, &d->bindings) {

+ insert_PtrSet(&d->lookup, i.value);

+ }

+}

+

+void setKey_Binding(int id, int key, int mods) {

+ iBinding *bind = findId_Keys_(&keys_, id);

+ if (bind) {

+ bind->key = key;

+ bind->mods = mods;

+ updateLookup_Keys_(&keys_);

+ }

+}

+

/----------------------------------------------------------------------------------------------/



void init_Keys(void) {

 iKeys *d = &keys_;

- init_SortedArray(&d->bindings, sizeof(iBinding), cmp_Binding_);

+ init_Array(&d->bindings, sizeof(iBinding));

+ initCmp_PtrSet(&d->lookup, cmpPtr_Binding_);

 bindDefaults_();

+ updateLookup_Keys_(d);

}



void deinit_Keys(void) {

 iKeys *d = &keys_;

 clear_Keys_(d);

- deinit_SortedArray(&d->bindings);

+ deinit_PtrSet(&d->lookup);

+ deinit_Array(&d->bindings);

}



void load_Keys(const char *saveDir) {

@@ -103,34 +139,42 @@ void save_Keys(const char *saveDir) {



}



-void bind_Keys(const char *command, int key, int mods) {

+void bind_Keys(int id, const char *command, int key, int mods) {

 iKeys *d = &keys_;

- iBinding *bind = find_Keys_(d, key, mods);

- if (bind) {

- setCStr_String(&bind->command, command);

+ iBinding *bind = findId_Keys_(d, id);

+ if (!bind) {

+ iBinding elem = { .id = id, .key = key, .mods = mods };

+ initCStr_String(&elem.command, command);

+ init_String(&elem.label);

+ pushBack_Array(&d->bindings, &elem);

 }

 else {

- iBinding bind;

- bind.key = key;

- bind.mods = mods;

- initCStr_String(&bind.command, command);

- init_String(&bind.label);

- insert_SortedArray(&d->bindings, &bind);

+ setCStr_String(&bind->command, command);

+ bind->key = key;

+ bind->mods = mods;

 }

}



-void setLabel_Keys(const char *command, const char *label) {

- iBinding *bind = findCommand_Keys_(&keys_, command);

+void setLabel_Keys(int id, const char *label) {

+ iBinding *bind = findId_Keys_(&keys_, id);

 if (bind) {

     setCStr_String(&bind->label, label);

 }

}



-//const iString *label_Keys(const char *command) {

-

-//}

-

-//const char *shortcutLabel_Keys(const char *command) {}

+#if 0

+const iString *label_Keys(const char *command) {

+ iKeys *d = &keys_;

+ /* TODO: A hash wouldn't hurt here. */

+ iConstForEach(PtrSet, i, &d->bindings) {

+ const iBinding *bind = *i.value;

+ if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {

+ return &bind->label;

+ }

+ }

+ return collectNew_String();

+}

+#endif



iBool processEvent_Keys(const SDL_Event *ev) {

 iKeys *d = &keys_;

@@ -147,3 +191,12 @@ iBool processEvent_Keys(const SDL_Event *ev) {

const iBinding *findCommand_Keys(const char *command) {

 return findCommand_Keys_(&keys_, command);

}

+

+const iPtrArray *list_Keys(void) {

+ iKeys *d = &keys_;

+ iPtrArray *list = collectNew_PtrArray();

+ iConstForEach(Array, i, &d->bindings) {

+ pushBack_PtrArray(list, i.value);

+ }

+ return list;

+}

diff --git a/src/ui/keys.h b/src/ui/keys.h

index 0892bd81..a4c8f348 100644

--- a/src/ui/keys.h

+++ b/src/ui/keys.h

@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

#pragma once



#include <the_Foundation/string.h>

+#include <the_Foundation/ptrarray.h>

#include <SDL_events.h>



#if defined (iPlatformApple)

@@ -46,23 +47,32 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

iDeclareType(Binding)



struct Impl_Binding {

+ int id;

 int key;

 int mods;

 iString command;

 iString label;

};



+void setKey_Binding (int id, int key, int mods);

+

+/----------------------------------------------------------------------------------------------/

+

void init_Keys (void);

void deinit_Keys (void);



void load_Keys (const char *saveDir);

void save_Keys (const char *saveDir);



-void bind_Keys (const char *command, int key, int mods);

-void setLabel_Keys (const char *command, const char *label);

-const iBinding *findCommand_Keys (const char *command);

+void bind_Keys (int id, const char *command, int key, int mods);

+void setLabel_Keys (int id, const char *label);



-//const iString * label_Keys (const char *command);

-//const char * shortcutLabel_Keys (const char *command);

+iLocalDef void bindLabel_Keys(int id, const char *command, int key, int mods, const char *label) {

+ bind_Keys(id, command, key, mods);

+ setLabel_Keys(id, label);

+}

+

+const iBinding *findCommand_Keys (const char *command);



iBool processEvent_Keys (const SDL_Event *);

+const iPtrArray *list_Keys (void);

diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c

index fb328c2f..02e1c728 100644

--- a/src/ui/listwidget.c

+++ b/src/ui/listwidget.c

@@ -120,6 +120,9 @@ void updateVisible_ListWidget(iListWidget *d) {

 const int   contentSize = size_PtrArray(&d->items) * d->itemHeight;

 const iRect bounds      = innerBounds_Widget(as_Widget(d));

 const iBool wasVisible  = isVisible_Widget(d->scroll);

+ if (area_Rect(bounds) == 0) {

+ return;

+ }

 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) });

 setThumb_ScrollWidget(d->scroll,

                       d->scrollY,

@@ -245,11 +248,21 @@ void updateMouseHover_ListWidget(iListWidget *d) {

 setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse));

}



+void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) {

+ sort_Array(&d->items, (iSortedArrayCompareElemFunc) cmp);

+}

+

static void redrawHoverItem_ListWidget_(iListWidget *d) {

 insert_IntSet(&d->invalidItems, d->hoverItem);

 refresh_Widget(as_Widget(d));

}



+static void sizeChanged_ListWidget_(iListWidget *d) {

+ printf("ListWidget %p size changed: %d x %d\n", d, d->widget.rect.size.x, d->widget.rect.size.y); fflush(stdout);

+ updateVisible_ListWidget(d);

+ invalidate_ListWidget(d);

+}

+

static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {

 iWidget *w = as_Widget(d);

 if (isCommand_SDLEvent(ev)) {

@@ -391,4 +404,5 @@ iBool isMouseDown_ListWidget(const iListWidget *d) {

iBeginDefineSubclass(ListWidget, Widget)

 .processEvent = (iAny *) processEvent_ListWidget_,

 .draw         = (iAny *) draw_ListWidget_,

+ .sizeChanged = (iAny *) sizeChanged_ListWidget_,

iEndDefineSubclass(ListWidget)

diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h

index da6303e9..11f1672e 100644

--- a/src/ui/listwidget.h

+++ b/src/ui/listwidget.h

@@ -64,6 +64,8 @@ void scrollOffset_ListWidget (iListWidget *, int offset);

void updateVisible_ListWidget (iListWidget *);

void updateMouseHover_ListWidget (iListWidget *);



+void sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2));

+

iAnyObject * item_ListWidget (iListWidget *, size_t index);

iAnyObject * hoverItem_ListWidget (iListWidget *);



diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c

index d6292f5b..c8571589 100644

--- a/src/ui/sidebarwidget.c

+++ b/src/ui/sidebarwidget.c

@@ -485,8 +485,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)

 /* Handle commands. */

 if (isResize_UserEvent(ev)) {

     checkModeButtonLayout_SidebarWidget_(d);

- updateVisible_ListWidget(d->list);

- invalidate_ListWidget(d->list);

 }

 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {

     const char *cmd = command_UserEvent(ev);

diff --git a/src/ui/util.c b/src/ui/util.c

index 44f7e089..ceab01b8 100644

--- a/src/ui/util.c

+++ b/src/ui/util.c

@@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

#include "gmutil.h"

#include "labelwidget.h"

#include "inputwidget.h"

+#include "bindingswidget.h"

#include "keys.h"

#include "widget.h"

#include "text.h"

@@ -105,6 +106,11 @@ void toString_Sym(int key, int kmods, iString *str) {

 }

}



+iBool isMod_Sym(int key) {

+ return key == SDLK_LALT || key == SDLK_RALT || key == SDLK_LCTRL || key == SDLK_RCTRL ||

+ key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;

+}

+

int keyMods_Sym(int kmods) {

 kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);

 /* Don't treat left/right modifiers differently. */

@@ -920,12 +926,22 @@ iWidget *makeToggle_Widget(const char *id) {

 return toggle;

}



+static void appendFramelessTabPage_(iWidget *tabs, iWidget *page, const char *title, int shortcut,

+ int kmods) {

+ appendTabPage_Widget(tabs, page, title, shortcut, kmods);

+ setFlags_Widget(

+ (iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),

+ frameless_WidgetFlag,

+ iTrue);

+}

+

static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int shortcut, iWidget **headings,

                                  iWidget **values) {

 iWidget *page = new_Widget();

 setFlags_Widget(page, arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |

                 resizeHeightOfChildren_WidgetFlag | borderTop_WidgetFlag, iTrue);

 addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);

+ setPadding_Widget(page, 0, gap_UI, 0, gap_UI);

 iWidget *columns = new_Widget();

 addChildFlags_Widget(page, iClob(columns), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);

 *headings = addChildFlags_Widget(

@@ -933,11 +949,7 @@ static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int short

 *values = addChildFlags_Widget(

     columns, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);

 addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);

- appendTabPage_Widget(tabs, page, title, shortcut, shortcut ? KMOD_PRIMARY : 0);

- setFlags_Widget(

- (iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),

- frameless_WidgetFlag,

- iTrue);

+ appendFramelessTabPage_(tabs, page, title, shortcut, shortcut ? KMOD_PRIMARY : 0);

 return page;

}



@@ -1080,6 +1092,11 @@ iWidget *makePreferences_Widget(void) {

     addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:")));

     setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http");

 }

+ /* Keybindings. */ {

+ iBindingsWidget *bind = new_BindingsWidget();

+ setFlags_Widget(as_Widget(bind), borderTop_WidgetFlag, iTrue);

+ appendFramelessTabPage_(tabs, iClob(bind), "Bindings", '5', KMOD_PRIMARY);

+ }

 resizeToLargestPage_Widget(tabs);

 arrange_Widget(dlg);

 /* Set input field sizes. */ {

diff --git a/src/ui/util.h b/src/ui/util.h

index 9796b387..c0e3a04c 100644

--- a/src/ui/util.h

+++ b/src/ui/util.h

@@ -48,6 +48,7 @@ iLocalDef iBool isResize_UserEvent(const SDL_Event *d) {

define KMOD_SECONDARY KMOD_GUI

#endif



+iBool isMod_Sym (int key);

int keyMods_Sym (int kmods); /* shift, alt, control, or gui */

void toString_Sym (int key, int kmods, iString *str);



Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/release/pcdiff/f809b80a688b8cd0a5a8bcb578d4cce1562fff5e
Status Code
Success (20)
Meta
text/plain
Capsule Response Time
29.91086 milliseconds
Gemini-to-HTML Time
14.207891 milliseconds

This content has been proxied by September (ba2dc).