[1mdiff --git a/CMakeLists.txt b/CMakeLists.txt[m
[1mindex 7d73956d..cfd42044 100644[m
[1m--- a/CMakeLists.txt[m
[1m+++ b/CMakeLists.txt[m
[36m@@ -123,6 +123,8 @@[m [mset (SOURCES[m
src/audio/player.h[m
src/audio/stb_vorbis.c[m
# User interface:[m
[32m+[m[32m src/ui/bindingswidget.c[m
[32m+[m[32m src/ui/bindingswidget.h[m
src/ui/color.c[m
src/ui/color.h[m
src/ui/command.c[m
[1mdiff --git a/src/app.c b/src/app.c[m
[1mindex 10f5f0d2..311a88bb 100644[m
[1m--- a/src/app.c[m
[1m+++ b/src/app.c[m
[36m@@ -430,6 +430,7 @@[m [mstatic void init_App_(iApp *d, int argc, char **argv) {[m
static void deinit_App(iApp *d) {[m
saveState_App_(d);[m
save_Keys(dataDir_App_);[m
[32m+[m[32m deinit_Keys();[m
savePrefs_App_(d);[m
deinit_Prefs(&d->prefs);[m
save_Bookmarks(d->bookmarks, dataDir_App_);[m
[1mdiff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c[m
[1mnew file mode 100644[m
[1mindex 00000000..4ce6ea4d[m
[1m--- /dev/null[m
[1m+++ b/src/ui/bindingswidget.c[m
[36m@@ -0,0 +1,193 @@[m
[32m+[m[32m/* Copyright 2020 Jaakko Keränen jaakko.keranen@iki.fi[m
[32m+[m
[32m+[m[32mRedistribution and use in source and binary forms, with or without[m
[32m+[m[32mmodification, are permitted provided that the following conditions are met:[m
[32m+[m
[32m+[m[32m1. Redistributions of source code must retain the above copyright notice, this[m
[32m+[m[32m list of conditions and the following disclaimer.[m
[32m+[m[32m2. Redistributions in binary form must reproduce the above copyright notice,[m
[32m+[m[32m this list of conditions and the following disclaimer in the documentation[m
[32m+[m[32m and/or other materials provided with the distribution.[m
[32m+[m
[32m+[m[32mTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND[m
[32m+[m[32mANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED[m
[32m+[m[32mWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE[m
[32m+[m[32mDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR[m
[32m+[m[32mANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES[m
[32m+[m[32m(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;[m
[32m+[m[32mLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON[m
[32m+[m[32mANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT[m
[32m+[m[32m(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS[m
[32m+[m[32mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
[32m+[m
[32m+[m[32m#include "bindingswidget.h"[m
[32m+[m[32m#include "listwidget.h"[m
[32m+[m[32m#include "keys.h"[m
[32m+[m[32m#include "command.h"[m
[32m+[m[32m#include "util.h"[m
[32m+[m[32m#include "app.h"[m
[32m+[m
[32m+[m[32miDeclareType(BindingItem)[m
[32m+[m[32mtypedef iListItemClass iBindingItemClass;[m
[32m+[m
[32m+[m[32mstruct Impl_BindingItem {[m
[32m+[m[32m iListItem listItem;[m
[32m+[m[32m iString label;[m
[32m+[m[32m iString key;[m
[32m+[m[32m int id;[m
[32m+[m[32m iBool isWaitingForEvent;[m
[32m+[m[32m};[m
[32m+[m
[32m+[m[32mvoid init_BindingItem(iBindingItem *d) {[m
[32m+[m[32m init_ListItem(&d->listItem);[m
[32m+[m[32m init_String(&d->label);[m
[32m+[m[32m init_String(&d->key);[m
[32m+[m[32m d->id = 0;[m
[32m+[m[32m d->isWaitingForEvent = iFalse;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mvoid deinit_BindingItem(iBindingItem *d) {[m
[32m+[m[32m deinit_String(&d->key);[m
[32m+[m[32m deinit_String(&d->label);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void setKey_BindingItem_(iBindingItem *d, int key, int mods) {[m
[32m+[m[32m setKey_Binding(d->id, key, mods);[m
[32m+[m[32m clear_String(&d->key);[m
[32m+[m[32m toString_Sym(key, mods, &d->key);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,[m
[32m+[m[32m const iListWidget *list);[m
[32m+[m
[32m+[m[32miBeginDefineSubclass(BindingItem, ListItem)[m
[32m+[m[32m .draw = (iAny *) draw_BindingItem_,[m
[32m+[m[32miEndDefineSubclass(BindingItem)[m
[32m+[m
[32m+[m[32miDefineObjectConstruction(BindingItem)[m
[32m+[m
[32m+[m[32m/----------------------------------------------------------------------------------------------/[m
[32m+[m
[32m+[m[32mstruct Impl_BindingsWidget {[m
[32m+[m[32m iWidget widget;[m
[32m+[m[32m iListWidget *list;[m
[32m+[m[32m size_t activePos;[m
[32m+[m[32m};[m
[32m+[m
[32m+[m[32miDefineObjectConstruction(BindingsWidget)[m
[32m+[m
[32m+[m[32mstatic int cmpId_BindingItem_(const iListItem **item1, const iListItem **item2) {[m
[32m+[m[32m const iBindingItem *d = (const iBindingItem *) *item1, *other = (const iBindingItem *) *item2;[m
[32m+[m[32m return iCmp(d->id, other->id);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void updateItems_BindingsWidget_(iBindingsWidget *d) {[m
[32m+[m[32m clear_ListWidget(d->list);[m
[32m+[m[32m iConstForEach(PtrArray, i, list_Keys()) {[m
[32m+[m[32m const iBinding *bind = i.ptr;[m
[32m+[m[32m if (isEmpty_String(&bind->label)) {[m
[32m+[m[32m /* Only the ones with label are user-changeable. */[m
[32m+[m[32m continue;[m
[32m+[m[32m }[m
[32m+[m[32m iBindingItem *item = new_BindingItem();[m
[32m+[m[32m item->id = bind->id;[m
[32m+[m[32m set_String(&item->label, &bind->label);[m
[32m+[m[32m toString_Sym(bind->key, bind->mods, &item->key);[m
[32m+[m[32m addItem_ListWidget(d->list, item);[m
[32m+[m[32m }[m
[32m+[m[32m sort_ListWidget(d->list, cmpId_BindingItem_);[m
[32m+[m[32m updateVisible_ListWidget(d->list);[m
[32m+[m[32m invalidate_ListWidget(d->list);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mvoid init_BindingsWidget(iBindingsWidget *d) {[m
[32m+[m[32m iWidget *w = as_Widget(d);[m
[32m+[m[32m init_Widget(w);[m
[32m+[m[32m setFlags_Widget(w, resizeChildren_WidgetFlag, iTrue);[m
[32m+[m[32m d->activePos = iInvalidPos;[m
[32m+[m[32m d->list = new_ListWidget();[m
[32m+[m[32m setItemHeight_ListWidget(d->list, lineHeight_Text(uiLabel_FontId) * 1.5f);[m
[32m+[m[32m addChild_Widget(w, iClob(d->list));[m
[32m+[m[32m updateItems_BindingsWidget_(d);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mvoid deinit_BindingsWidget(iBindingsWidget *d) {[m
[32m+[m[32m /* nothing to do */[m
[32m+[m[32m iUnused(d);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) {[m
[32m+[m[32m if (d->activePos != iInvalidPos) {[m
[32m+[m[32m iBindingItem *item = item_ListWidget(d->list, d->activePos);[m
[32m+[m[32m item->isWaitingForEvent = iFalse;[m
[32m+[m[32m invalidateItem_ListWidget(d->list, d->activePos);[m
[32m+[m[32m }[m
[32m+[m[32m d->activePos = pos;[m
[32m+[m[32m if (d->activePos != iInvalidPos) {[m
[32m+[m[32m iBindingItem *item = item_ListWidget(d->list, d->activePos);[m
[32m+[m[32m item->isWaitingForEvent = iTrue;[m
[32m+[m[32m invalidateItem_ListWidget(d->list, d->activePos);[m
[32m+[m[32m }[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *ev) {[m
[32m+[m[32m iWidget * w = as_Widget(d);[m
[32m+[m[32m const char *cmd = command_UserEvent(ev);[m
[32m+[m[32m if (isCommand_Widget(w, ev, "list.clicked")) {[m
[32m+[m[32m setActiveItem_BindingsWidget_(d, arg_Command(cmd));[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
[32m+[m[32m /* Waiting for a keypress? */[m
[32m+[m[32m if (d->activePos != iInvalidPos) {[m
[32m+[m[32m if (ev->type == SDL_KEYDOWN && !isMod_Sym(ev->key.keysym.sym)) {[m
[32m+[m[32m setKey_BindingItem_(item_ListWidget(d->list, d->activePos),[m
[32m+[m[32m ev->key.keysym.sym,[m
[32m+[m[32m keyMods_Sym(ev->key.keysym.mod));[m
[32m+[m[32m setActiveItem_BindingsWidget_(d, iInvalidPos);[m
[32m+[m[32m postCommand_App("bindings.changed");[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m return processEvent_Widget(w, ev);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void draw_BindingsWidget_(const iBindingsWidget *d) {[m
[32m+[m[32m const iWidget *w = constAs_Widget(d);[m
[32m+[m[32m drawChildren_Widget(w);[m
[32m+[m[32m drawBackground_Widget(w); /* kludge to allow drawing a top border over the list */[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,[m
[32m+[m[32m const iListWidget *list) {[m
[32m+[m[32m const int font = uiLabel_FontId;[m
[32m+[m[32m const int itemHeight = height_Rect(itemRect);[m
[32m+[m[32m const int line = lineHeight_Text(font);[m
[32m+[m[32m int fg = uiText_ColorId;[m
[32m+[m[32m const iBool isPressing = isMouseDown_ListWidget(list) || d->isWaitingForEvent;[m
[32m+[m[32m const iBool isHover = (isHover_Widget(constAs_Widget(list)) &&[m
[32m+[m[32m constHoverItem_ListWidget(list) == d);[m
[32m+[m[32m if (isHover || isPressing) {[m
[32m+[m[32m fg = isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId;[m
[32m+[m[32m fillRect_Paint(p,[m
[32m+[m[32m itemRect,[m
[32m+[m[32m isPressing ? uiBackgroundPressed_ColorId[m
[32m+[m[32m : uiBackgroundFramelessHover_ColorId);[m
[32m+[m[32m }[m
[32m+[m[32m const int y = top_Rect(itemRect) + (itemHeight - line) / 2;[m
[32m+[m[32m drawRange_Text(font,[m
[32m+[m[32m init_I2(left_Rect(itemRect) + 3 * gap_UI, y),[m
[32m+[m[32m fg,[m
[32m+[m[32m range_String(&d->label));[m
[32m+[m[32m drawAlign_Text(d->isWaitingForEvent ? uiContent_FontId : font,[m
[32m+[m[32m init_I2(right_Rect(itemRect) - 3 * gap_UI,[m
[32m+[m[32m y - (lineHeight_Text(uiContent_FontId) - line) / 2),[m
[32m+[m[32m fg,[m
[32m+[m[32m right_Alignment,[m
[32m+[m[32m "%s",[m
[32m+[m[32m d->isWaitingForEvent ? "\U0001F449 \u2328" : cstr_String(&d->key));[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32miBeginDefineSubclass(BindingsWidget, Widget)[m
[32m+[m[32m .processEvent = (iAny *) processEvent_BindingsWidget_,[m
[32m+[m[32m .draw = (iAny *) draw_BindingsWidget_,[m
[32m+[m[32miEndDefineSubclass(BindingsWidget)[m
[1mdiff --git a/src/ui/bindingswidget.h b/src/ui/bindingswidget.h[m
[1mnew file mode 100644[m
[1mindex 00000000..e1ed402c[m
[1m--- /dev/null[m
[1m+++ b/src/ui/bindingswidget.h[m
[36m@@ -0,0 +1,28 @@[m
[32m+[m[32m/* Copyright 2020 Jaakko Keränen jaakko.keranen@iki.fi[m
[32m+[m
[32m+[m[32mRedistribution and use in source and binary forms, with or without[m
[32m+[m[32mmodification, are permitted provided that the following conditions are met:[m
[32m+[m
[32m+[m[32m1. Redistributions of source code must retain the above copyright notice, this[m
[32m+[m[32m list of conditions and the following disclaimer.[m
[32m+[m[32m2. Redistributions in binary form must reproduce the above copyright notice,[m
[32m+[m[32m this list of conditions and the following disclaimer in the documentation[m
[32m+[m[32m and/or other materials provided with the distribution.[m
[32m+[m
[32m+[m[32mTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND[m
[32m+[m[32mANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED[m
[32m+[m[32mWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE[m
[32m+[m[32mDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR[m
[32m+[m[32mANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES[m
[32m+[m[32m(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;[m
[32m+[m[32mLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON[m
[32m+[m[32mANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT[m
[32m+[m[32m(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS[m
[32m+[m[32mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
[32m+[m
[32m+[m[32m#pragma once[m
[32m+[m
[32m+[m[32m#include "widget.h"[m
[32m+[m
[32m+[m[32miDeclareWidgetClass(BindingsWidget)[m
[32m+[m[32miDeclareObjectConstruction(BindingsWidget)[m
[1mdiff --git a/src/ui/keys.c b/src/ui/keys.c[m
[1mindex 85304ef7..cc7dabef 100644[m
[1m--- a/src/ui/keys.c[m
[1m+++ b/src/ui/keys.c[m
[36m@@ -24,12 +24,12 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
#include "util.h"[m
#include "app.h"[m
[m
[31m-#include <the_Foundation/sortedarray.h>[m
[32m+[m[32m#include <the_Foundation/ptrset.h>[m
[m
iDeclareType(Keys)[m
[m
[31m-static int cmp_Binding_(const void *a, const void *b) {[m
[31m- const iBinding *d = a, *other = b;[m
[32m+[m[32mstatic int cmpPtr_Binding_(const void *a, const void *b) {[m
[32m+[m[32m const iBinding *d = *(const void **) a, *other = *(const void **) b;[m
const int cmp = iCmp(d->key, other->key);[m
if (cmp == 0) {[m
return iCmp(d->mods, other->mods);[m
[36m@@ -37,14 +37,17 @@[m [mstatic int cmp_Binding_(const void *a, const void *b) {[m
return cmp;[m
}[m
[m
[32m+[m[32m/----------------------------------------------------------------------------------------------/[m
[32m+[m
struct Impl_Keys {[m
[31m- iSortedArray bindings;[m
[32m+[m[32m iArray bindings;[m
[32m+[m[32m iPtrSet lookup; /* quick key/mods lookup */[m
};[m
[m
static iKeys keys_;[m
[m
static void clear_Keys_(iKeys *d) {[m
[31m- iForEach(Array, i, &d->bindings.values) {[m
[32m+[m[32m iForEach(Array, i, &d->bindings) {[m
iBinding *bind = i.value;[m
deinit_String(&bind->command);[m
deinit_String(&bind->label);[m
[36m@@ -52,27 +55,41 @@[m [mstatic void clear_Keys_(iKeys *d) {[m
}[m
[m
static void bindDefaults_(void) {[m
[31m- bind_Keys("scroll.top", SDLK_HOME, 0);[m
[31m- bind_Keys("scroll.bottom", SDLK_END, 0);[m
[31m- bind_Keys("scroll.step arg:-1", SDLK_UP, 0);[m
[31m- bind_Keys("scroll.step arg:1", SDLK_DOWN, 0);[m
[31m- bind_Keys("scroll.page arg:-1", SDLK_PAGEUP, 0);[m
[31m- bind_Keys("scroll.page arg:1", SDLK_PAGEDOWN, 0);[m
[31m- bind_Keys("scroll.page arg:-1", SDLK_SPACE, KMOD_SHIFT);[m
[31m- bind_Keys("scroll.page arg:1", SDLK_SPACE, 0);[m
[32m+[m[32m /* TODO: This indirection could be used for localization, although all UI strings[m
[32m+[m[32m would need to be similarly handled. */[m
[32m+[m[32m bindLabel_Keys(1, "scroll.top", SDLK_HOME, 0, "Jump to top");[m
[32m+[m[32m bindLabel_Keys(2, "scroll.bottom", SDLK_END, 0, "Jump to bottom");[m
[32m+[m[32m bindLabel_Keys(10, "scroll.step arg:-1", SDLK_UP, 0, "Scroll up");[m
[32m+[m[32m bindLabel_Keys(11, "scroll.step arg:1", SDLK_DOWN, 0, "Scroll down");[m
[32m+[m[32m bindLabel_Keys(20, "scroll.page arg:-1", SDLK_PAGEUP, 0, "Scroll up half a page");[m
[32m+[m[32m bindLabel_Keys(21, "scroll.page arg:1", SDLK_PAGEDOWN, 0, "Scroll down half a page");[m
[32m+[m[32m /* The following cannot currently be changed (built-in duplicates). */[m
[32m+[m[32m bind_Keys(1000, "scroll.page arg:-1", SDLK_SPACE, KMOD_SHIFT);[m
[32m+[m[32m bind_Keys(1001, "scroll.page arg:1", SDLK_SPACE, 0);[m
}[m
[m
static iBinding *find_Keys_(iKeys *d, int key, int mods) {[m
size_t pos;[m
[31m- if (locate_SortedArray(&d->bindings, &(iBinding){ .key = key, .mods = mods }, &pos)) {[m
[31m- return at_SortedArray(&d->bindings, pos);[m
[32m+[m[32m const iBinding elem = { .key = key, .mods = mods };[m
[32m+[m[32m if (locate_PtrSet(&d->lookup, &elem, &pos)) {[m
[32m+[m[32m return at_PtrSet(&d->lookup, pos);[m
[32m+[m[32m }[m
[32m+[m[32m return NULL;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic iBinding *findId_Keys_(iKeys *d, int id) {[m
[32m+[m[32m iForEach(Array, i, &d->bindings) {[m
[32m+[m[32m iBinding *bind = i.value;[m
[32m+[m[32m if (bind->id == id) {[m
[32m+[m[32m return bind;[m
[32m+[m[32m }[m
}[m
return NULL;[m
}[m
[m
static iBinding *findCommand_Keys_(iKeys *d, const char *command) {[m
/* Note: O(n) */[m
[31m- iForEach(Array, i, &d->bindings.values) {[m
[32m+[m[32m iForEach(Array, i, &d->bindings) {[m
iBinding *bind = i.value;[m
if (!cmp_String(&bind->command, command)) {[m
return bind;[m
[36m@@ -81,18 +98,37 @@[m [mstatic iBinding *findCommand_Keys_(iKeys *d, const char *command) {[m
return NULL;[m
}[m
[m
[32m+[m[32mstatic void updateLookup_Keys_(iKeys *d) {[m
[32m+[m[32m clear_PtrSet(&d->lookup);[m
[32m+[m[32m iConstForEach(Array, i, &d->bindings) {[m
[32m+[m[32m insert_PtrSet(&d->lookup, i.value);[m
[32m+[m[32m }[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mvoid setKey_Binding(int id, int key, int mods) {[m
[32m+[m[32m iBinding *bind = findId_Keys_(&keys_, id);[m
[32m+[m[32m if (bind) {[m
[32m+[m[32m bind->key = key;[m
[32m+[m[32m bind->mods = mods;[m
[32m+[m[32m updateLookup_Keys_(&keys_);[m
[32m+[m[32m }[m
[32m+[m[32m}[m
[32m+[m
/----------------------------------------------------------------------------------------------/[m
[m
void init_Keys(void) {[m
iKeys *d = &keys_;[m
[31m- init_SortedArray(&d->bindings, sizeof(iBinding), cmp_Binding_);[m
[32m+[m[32m init_Array(&d->bindings, sizeof(iBinding));[m
[32m+[m[32m initCmp_PtrSet(&d->lookup, cmpPtr_Binding_);[m
bindDefaults_();[m
[32m+[m[32m updateLookup_Keys_(d);[m
}[m
[m
void deinit_Keys(void) {[m
iKeys *d = &keys_;[m
clear_Keys_(d);[m
[31m- deinit_SortedArray(&d->bindings);[m
[32m+[m[32m deinit_PtrSet(&d->lookup);[m
[32m+[m[32m deinit_Array(&d->bindings);[m
}[m
[m
void load_Keys(const char *saveDir) {[m
[36m@@ -103,34 +139,42 @@[m [mvoid save_Keys(const char *saveDir) {[m
[m
}[m
[m
[31m-void bind_Keys(const char *command, int key, int mods) {[m
[32m+[m[32mvoid bind_Keys(int id, const char *command, int key, int mods) {[m
iKeys *d = &keys_;[m
[31m- iBinding *bind = find_Keys_(d, key, mods);[m
[31m- if (bind) {[m
[31m- setCStr_String(&bind->command, command);[m
[32m+[m[32m iBinding *bind = findId_Keys_(d, id);[m
[32m+[m[32m if (!bind) {[m
[32m+[m[32m iBinding elem = { .id = id, .key = key, .mods = mods };[m
[32m+[m[32m initCStr_String(&elem.command, command);[m
[32m+[m[32m init_String(&elem.label);[m
[32m+[m[32m pushBack_Array(&d->bindings, &elem);[m
}[m
else {[m
[31m- iBinding bind;[m
[31m- bind.key = key;[m
[31m- bind.mods = mods;[m
[31m- initCStr_String(&bind.command, command);[m
[31m- init_String(&bind.label);[m
[31m- insert_SortedArray(&d->bindings, &bind);[m
[32m+[m[32m setCStr_String(&bind->command, command);[m
[32m+[m[32m bind->key = key;[m
[32m+[m[32m bind->mods = mods;[m
}[m
}[m
[m
[31m-void setLabel_Keys(const char *command, const char *label) {[m
[31m- iBinding *bind = findCommand_Keys_(&keys_, command);[m
[32m+[m[32mvoid setLabel_Keys(int id, const char *label) {[m
[32m+[m[32m iBinding *bind = findId_Keys_(&keys_, id);[m
if (bind) {[m
setCStr_String(&bind->label, label);[m
}[m
}[m
[m
[31m-//const iString *label_Keys(const char *command) {[m
[31m-[m
[31m-//}[m
[31m-[m
[31m-//const char *shortcutLabel_Keys(const char *command) {}[m
[32m+[m[32m#if 0[m
[32m+[m[32mconst iString *label_Keys(const char *command) {[m
[32m+[m[32m iKeys *d = &keys_;[m
[32m+[m[32m /* TODO: A hash wouldn't hurt here. */[m
[32m+[m[32m iConstForEach(PtrSet, i, &d->bindings) {[m
[32m+[m[32m const iBinding *bind = *i.value;[m
[32m+[m[32m if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {[m
[32m+[m[32m return &bind->label;[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m return collectNew_String();[m
[32m+[m[32m}[m
[32m+[m[32m#endif[m
[m
iBool processEvent_Keys(const SDL_Event *ev) {[m
iKeys *d = &keys_;[m
[36m@@ -147,3 +191,12 @@[m [miBool processEvent_Keys(const SDL_Event *ev) {[m
const iBinding *findCommand_Keys(const char *command) {[m
return findCommand_Keys_(&keys_, command);[m
}[m
[32m+[m
[32m+[m[32mconst iPtrArray *list_Keys(void) {[m
[32m+[m[32m iKeys *d = &keys_;[m
[32m+[m[32m iPtrArray *list = collectNew_PtrArray();[m
[32m+[m[32m iConstForEach(Array, i, &d->bindings) {[m
[32m+[m[32m pushBack_PtrArray(list, i.value);[m
[32m+[m[32m }[m
[32m+[m[32m return list;[m
[32m+[m[32m}[m
[1mdiff --git a/src/ui/keys.h b/src/ui/keys.h[m
[1mindex 0892bd81..a4c8f348 100644[m
[1m--- a/src/ui/keys.h[m
[1m+++ b/src/ui/keys.h[m
[36m@@ -23,6 +23,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
#pragma once[m
[m
#include <the_Foundation/string.h>[m
[32m+[m[32m#include <the_Foundation/ptrarray.h>[m
#include <SDL_events.h>[m
[m
#if defined (iPlatformApple)[m
[36m@@ -46,23 +47,32 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
iDeclareType(Binding)[m
[m
struct Impl_Binding {[m
[32m+[m[32m int id;[m
int key;[m
int mods;[m
iString command;[m
iString label;[m
};[m
[m
[32m+[m[32mvoid setKey_Binding (int id, int key, int mods);[m
[32m+[m
[32m+[m[32m/----------------------------------------------------------------------------------------------/[m
[32m+[m
void init_Keys (void);[m
void deinit_Keys (void);[m
[m
void load_Keys (const char *saveDir);[m
void save_Keys (const char *saveDir);[m
[m
[31m-void bind_Keys (const char *command, int key, int mods);[m
[31m-void setLabel_Keys (const char *command, const char *label);[m
[31m-const iBinding *findCommand_Keys (const char *command);[m
[32m+[m[32mvoid bind_Keys (int id, const char *command, int key, int mods);[m
[32m+[m[32mvoid setLabel_Keys (int id, const char *label);[m
[m
[31m-//const iString * label_Keys (const char *command);[m
[31m-//const char * shortcutLabel_Keys (const char *command);[m
[32m+[m[32miLocalDef void bindLabel_Keys(int id, const char *command, int key, int mods, const char *label) {[m
[32m+[m[32m bind_Keys(id, command, key, mods);[m
[32m+[m[32m setLabel_Keys(id, label);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mconst iBinding *findCommand_Keys (const char *command);[m
[m
iBool processEvent_Keys (const SDL_Event *);[m
[32m+[m[32mconst iPtrArray *list_Keys (void);[m
[1mdiff --git a/src/ui/listwidget.c b/src/ui/listwidget.c[m
[1mindex fb328c2f..02e1c728 100644[m
[1m--- a/src/ui/listwidget.c[m
[1m+++ b/src/ui/listwidget.c[m
[36m@@ -120,6 +120,9 @@[m [mvoid updateVisible_ListWidget(iListWidget *d) {[m
const int contentSize = size_PtrArray(&d->items) * d->itemHeight;[m
const iRect bounds = innerBounds_Widget(as_Widget(d));[m
const iBool wasVisible = isVisible_Widget(d->scroll);[m
[32m+[m[32m if (area_Rect(bounds) == 0) {[m
[32m+[m[32m return;[m
[32m+[m[32m }[m
setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) });[m
setThumb_ScrollWidget(d->scroll,[m
d->scrollY,[m
[36m@@ -245,11 +248,21 @@[m [mvoid updateMouseHover_ListWidget(iListWidget *d) {[m
setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse));[m
}[m
[m
[32m+[m[32mvoid sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) {[m
[32m+[m[32m sort_Array(&d->items, (iSortedArrayCompareElemFunc) cmp);[m
[32m+[m[32m}[m
[32m+[m
static void redrawHoverItem_ListWidget_(iListWidget *d) {[m
insert_IntSet(&d->invalidItems, d->hoverItem);[m
refresh_Widget(as_Widget(d));[m
}[m
[m
[32m+[m[32mstatic void sizeChanged_ListWidget_(iListWidget *d) {[m
[32m+[m[32m printf("ListWidget %p size changed: %d x %d\n", d, d->widget.rect.size.x, d->widget.rect.size.y); fflush(stdout);[m
[32m+[m[32m updateVisible_ListWidget(d);[m
[32m+[m[32m invalidate_ListWidget(d);[m
[32m+[m[32m}[m
[32m+[m
static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {[m
iWidget *w = as_Widget(d);[m
if (isCommand_SDLEvent(ev)) {[m
[36m@@ -391,4 +404,5 @@[m [miBool isMouseDown_ListWidget(const iListWidget *d) {[m
iBeginDefineSubclass(ListWidget, Widget)[m
.processEvent = (iAny *) processEvent_ListWidget_,[m
.draw = (iAny *) draw_ListWidget_,[m
[32m+[m[32m .sizeChanged = (iAny *) sizeChanged_ListWidget_,[m
iEndDefineSubclass(ListWidget)[m
[1mdiff --git a/src/ui/listwidget.h b/src/ui/listwidget.h[m
[1mindex da6303e9..11f1672e 100644[m
[1m--- a/src/ui/listwidget.h[m
[1m+++ b/src/ui/listwidget.h[m
[36m@@ -64,6 +64,8 @@[m [mvoid scrollOffset_ListWidget (iListWidget *, int offset);[m
void updateVisible_ListWidget (iListWidget *);[m
void updateMouseHover_ListWidget (iListWidget *);[m
[m
[32m+[m[32mvoid sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2));[m
[32m+[m
iAnyObject * item_ListWidget (iListWidget *, size_t index);[m
iAnyObject * hoverItem_ListWidget (iListWidget *);[m
[m
[1mdiff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c[m
[1mindex d6292f5b..c8571589 100644[m
[1m--- a/src/ui/sidebarwidget.c[m
[1m+++ b/src/ui/sidebarwidget.c[m
[36m@@ -485,8 +485,6 @@[m [mstatic iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)[m
/* Handle commands. */[m
if (isResize_UserEvent(ev)) {[m
checkModeButtonLayout_SidebarWidget_(d);[m
[31m- updateVisible_ListWidget(d->list);[m
[31m- invalidate_ListWidget(d->list);[m
}[m
else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {[m
const char *cmd = command_UserEvent(ev);[m
[1mdiff --git a/src/ui/util.c b/src/ui/util.c[m
[1mindex 44f7e089..ceab01b8 100644[m
[1m--- a/src/ui/util.c[m
[1m+++ b/src/ui/util.c[m
[36m@@ -29,6 +29,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
#include "gmutil.h"[m
#include "labelwidget.h"[m
#include "inputwidget.h"[m
[32m+[m[32m#include "bindingswidget.h"[m
#include "keys.h"[m
#include "widget.h"[m
#include "text.h"[m
[36m@@ -105,6 +106,11 @@[m [mvoid toString_Sym(int key, int kmods, iString *str) {[m
}[m
}[m
[m
[32m+[m[32miBool isMod_Sym(int key) {[m
[32m+[m[32m return key == SDLK_LALT || key == SDLK_RALT || key == SDLK_LCTRL || key == SDLK_RCTRL ||[m
[32m+[m[32m key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;[m
[32m+[m[32m}[m
[32m+[m
int keyMods_Sym(int kmods) {[m
kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);[m
/* Don't treat left/right modifiers differently. */[m
[36m@@ -920,12 +926,22 @@[m [miWidget *makeToggle_Widget(const char *id) {[m
return toggle;[m
}[m
[m
[32m+[m[32mstatic void appendFramelessTabPage_(iWidget *tabs, iWidget *page, const char *title, int shortcut,[m
[32m+[m[32m int kmods) {[m
[32m+[m[32m appendTabPage_Widget(tabs, page, title, shortcut, kmods);[m
[32m+[m[32m setFlags_Widget([m
[32m+[m[32m (iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),[m
[32m+[m[32m frameless_WidgetFlag,[m
[32m+[m[32m iTrue);[m
[32m+[m[32m}[m
[32m+[m
static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int shortcut, iWidget **headings,[m
iWidget **values) {[m
iWidget *page = new_Widget();[m
setFlags_Widget(page, arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |[m
resizeHeightOfChildren_WidgetFlag | borderTop_WidgetFlag, iTrue);[m
addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);[m
[32m+[m[32m setPadding_Widget(page, 0, gap_UI, 0, gap_UI);[m
iWidget *columns = new_Widget();[m
addChildFlags_Widget(page, iClob(columns), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);[m
*headings = addChildFlags_Widget([m
[36m@@ -933,11 +949,7 @@[m [mstatic iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int short[m
*values = addChildFlags_Widget([m
columns, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);[m
addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);[m
[31m- appendTabPage_Widget(tabs, page, title, shortcut, shortcut ? KMOD_PRIMARY : 0);[m
[31m- setFlags_Widget([m
[31m- (iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),[m
[31m- frameless_WidgetFlag,[m
[31m- iTrue);[m
[32m+[m[32m appendFramelessTabPage_(tabs, page, title, shortcut, shortcut ? KMOD_PRIMARY : 0);[m
return page;[m
}[m
[m
[36m@@ -1080,6 +1092,11 @@[m [miWidget *makePreferences_Widget(void) {[m
addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:")));[m
setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http");[m
}[m
[32m+[m[32m /* Keybindings. */ {[m
[32m+[m[32m iBindingsWidget *bind = new_BindingsWidget();[m
[32m+[m[32m setFlags_Widget(as_Widget(bind), borderTop_WidgetFlag, iTrue);[m
[32m+[m[32m appendFramelessTabPage_(tabs, iClob(bind), "Bindings", '5', KMOD_PRIMARY);[m
[32m+[m[32m }[m
resizeToLargestPage_Widget(tabs);[m
arrange_Widget(dlg);[m
/* Set input field sizes. */ {[m
[1mdiff --git a/src/ui/util.h b/src/ui/util.h[m
[1mindex 9796b387..c0e3a04c 100644[m
[1m--- a/src/ui/util.h[m
[1m+++ b/src/ui/util.h[m
[36m@@ -48,6 +48,7 @@[m [miLocalDef iBool isResize_UserEvent(const SDL_Event *d) {[m
#endif[m
[m
[32m+[m[32miBool isMod_Sym (int key);[m
int keyMods_Sym (int kmods); /* shift, alt, control, or gui */[m
void toString_Sym (int key, int kmods, iString *str);[m
[m
text/plain
This content has been proxied by September (ba2dc).