summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/app.c1
-rw-r--r--src/ui/bindingswidget.c193
-rw-r--r--src/ui/bindingswidget.h28
-rw-r--r--src/ui/keys.c123
-rw-r--r--src/ui/keys.h20
-rw-r--r--src/ui/listwidget.c14
-rw-r--r--src/ui/listwidget.h2
-rw-r--r--src/ui/sidebarwidget.c2
-rw-r--r--src/ui/util.c27
-rw-r--r--src/ui/util.h1
11 files changed, 366 insertions, 47 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d73956d..cfd42044 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -123,6 +123,8 @@ set (SOURCES
123 src/audio/player.h 123 src/audio/player.h
124 src/audio/stb_vorbis.c 124 src/audio/stb_vorbis.c
125 # User interface: 125 # User interface:
126 src/ui/bindingswidget.c
127 src/ui/bindingswidget.h
126 src/ui/color.c 128 src/ui/color.c
127 src/ui/color.h 129 src/ui/color.h
128 src/ui/command.c 130 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) {
430static void deinit_App(iApp *d) { 430static void deinit_App(iApp *d) {
431 saveState_App_(d); 431 saveState_App_(d);
432 save_Keys(dataDir_App_); 432 save_Keys(dataDir_App_);
433 deinit_Keys();
433 savePrefs_App_(d); 434 savePrefs_App_(d);
434 deinit_Prefs(&d->prefs); 435 deinit_Prefs(&d->prefs);
435 save_Bookmarks(d->bookmarks, dataDir_App_); 436 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 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "bindingswidget.h"
24#include "listwidget.h"
25#include "keys.h"
26#include "command.h"
27#include "util.h"
28#include "app.h"
29
30iDeclareType(BindingItem)
31typedef iListItemClass iBindingItemClass;
32
33struct Impl_BindingItem {
34 iListItem listItem;
35 iString label;
36 iString key;
37 int id;
38 iBool isWaitingForEvent;
39};
40
41void init_BindingItem(iBindingItem *d) {
42 init_ListItem(&d->listItem);
43 init_String(&d->label);
44 init_String(&d->key);
45 d->id = 0;
46 d->isWaitingForEvent = iFalse;
47}
48
49void deinit_BindingItem(iBindingItem *d) {
50 deinit_String(&d->key);
51 deinit_String(&d->label);
52}
53
54static void setKey_BindingItem_(iBindingItem *d, int key, int mods) {
55 setKey_Binding(d->id, key, mods);
56 clear_String(&d->key);
57 toString_Sym(key, mods, &d->key);
58}
59
60static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,
61 const iListWidget *list);
62
63iBeginDefineSubclass(BindingItem, ListItem)
64 .draw = (iAny *) draw_BindingItem_,
65iEndDefineSubclass(BindingItem)
66
67iDefineObjectConstruction(BindingItem)
68
69/*----------------------------------------------------------------------------------------------*/
70
71struct Impl_BindingsWidget {
72 iWidget widget;
73 iListWidget *list;
74 size_t activePos;
75};
76
77iDefineObjectConstruction(BindingsWidget)
78
79static int cmpId_BindingItem_(const iListItem **item1, const iListItem **item2) {
80 const iBindingItem *d = (const iBindingItem *) *item1, *other = (const iBindingItem *) *item2;
81 return iCmp(d->id, other->id);
82}
83
84static void updateItems_BindingsWidget_(iBindingsWidget *d) {
85 clear_ListWidget(d->list);
86 iConstForEach(PtrArray, i, list_Keys()) {
87 const iBinding *bind = i.ptr;
88 if (isEmpty_String(&bind->label)) {
89 /* Only the ones with label are user-changeable. */
90 continue;
91 }
92 iBindingItem *item = new_BindingItem();
93 item->id = bind->id;
94 set_String(&item->label, &bind->label);
95 toString_Sym(bind->key, bind->mods, &item->key);
96 addItem_ListWidget(d->list, item);
97 }
98 sort_ListWidget(d->list, cmpId_BindingItem_);
99 updateVisible_ListWidget(d->list);
100 invalidate_ListWidget(d->list);
101}
102
103void init_BindingsWidget(iBindingsWidget *d) {
104 iWidget *w = as_Widget(d);
105 init_Widget(w);
106 setFlags_Widget(w, resizeChildren_WidgetFlag, iTrue);
107 d->activePos = iInvalidPos;
108 d->list = new_ListWidget();
109 setItemHeight_ListWidget(d->list, lineHeight_Text(uiLabel_FontId) * 1.5f);
110 addChild_Widget(w, iClob(d->list));
111 updateItems_BindingsWidget_(d);
112}
113
114void deinit_BindingsWidget(iBindingsWidget *d) {
115 /* nothing to do */
116 iUnused(d);
117}
118
119static void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) {
120 if (d->activePos != iInvalidPos) {
121 iBindingItem *item = item_ListWidget(d->list, d->activePos);
122 item->isWaitingForEvent = iFalse;
123 invalidateItem_ListWidget(d->list, d->activePos);
124 }
125 d->activePos = pos;
126 if (d->activePos != iInvalidPos) {
127 iBindingItem *item = item_ListWidget(d->list, d->activePos);
128 item->isWaitingForEvent = iTrue;
129 invalidateItem_ListWidget(d->list, d->activePos);
130 }
131}
132
133static iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *ev) {
134 iWidget * w = as_Widget(d);
135 const char *cmd = command_UserEvent(ev);
136 if (isCommand_Widget(w, ev, "list.clicked")) {
137 setActiveItem_BindingsWidget_(d, arg_Command(cmd));
138 return iTrue;
139 }
140 /* Waiting for a keypress? */
141 if (d->activePos != iInvalidPos) {
142 if (ev->type == SDL_KEYDOWN && !isMod_Sym(ev->key.keysym.sym)) {
143 setKey_BindingItem_(item_ListWidget(d->list, d->activePos),
144 ev->key.keysym.sym,
145 keyMods_Sym(ev->key.keysym.mod));
146 setActiveItem_BindingsWidget_(d, iInvalidPos);
147 postCommand_App("bindings.changed");
148 return iTrue;
149 }
150 }
151 return processEvent_Widget(w, ev);
152}
153
154static void draw_BindingsWidget_(const iBindingsWidget *d) {
155 const iWidget *w = constAs_Widget(d);
156 drawChildren_Widget(w);
157 drawBackground_Widget(w); /* kludge to allow drawing a top border over the list */
158}
159
160static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,
161 const iListWidget *list) {
162 const int font = uiLabel_FontId;
163 const int itemHeight = height_Rect(itemRect);
164 const int line = lineHeight_Text(font);
165 int fg = uiText_ColorId;
166 const iBool isPressing = isMouseDown_ListWidget(list) || d->isWaitingForEvent;
167 const iBool isHover = (isHover_Widget(constAs_Widget(list)) &&
168 constHoverItem_ListWidget(list) == d);
169 if (isHover || isPressing) {
170 fg = isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId;
171 fillRect_Paint(p,
172 itemRect,
173 isPressing ? uiBackgroundPressed_ColorId
174 : uiBackgroundFramelessHover_ColorId);
175 }
176 const int y = top_Rect(itemRect) + (itemHeight - line) / 2;
177 drawRange_Text(font,
178 init_I2(left_Rect(itemRect) + 3 * gap_UI, y),
179 fg,
180 range_String(&d->label));
181 drawAlign_Text(d->isWaitingForEvent ? uiContent_FontId : font,
182 init_I2(right_Rect(itemRect) - 3 * gap_UI,
183 y - (lineHeight_Text(uiContent_FontId) - line) / 2),
184 fg,
185 right_Alignment,
186 "%s",
187 d->isWaitingForEvent ? "\U0001F449 \u2328" : cstr_String(&d->key));
188}
189
190iBeginDefineSubclass(BindingsWidget, Widget)
191 .processEvent = (iAny *) processEvent_BindingsWidget_,
192 .draw = (iAny *) draw_BindingsWidget_,
193iEndDefineSubclass(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 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#pragma once
24
25#include "widget.h"
26
27iDeclareWidgetClass(BindingsWidget)
28iDeclareObjectConstruction(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. */
24#include "util.h" 24#include "util.h"
25#include "app.h" 25#include "app.h"
26 26
27#include <the_Foundation/sortedarray.h> 27#include <the_Foundation/ptrset.h>
28 28
29iDeclareType(Keys) 29iDeclareType(Keys)
30 30
31static int cmp_Binding_(const void *a, const void *b) { 31static int cmpPtr_Binding_(const void *a, const void *b) {
32 const iBinding *d = a, *other = b; 32 const iBinding *d = *(const void **) a, *other = *(const void **) b;
33 const int cmp = iCmp(d->key, other->key); 33 const int cmp = iCmp(d->key, other->key);
34 if (cmp == 0) { 34 if (cmp == 0) {
35 return iCmp(d->mods, other->mods); 35 return iCmp(d->mods, other->mods);
@@ -37,14 +37,17 @@ static int cmp_Binding_(const void *a, const void *b) {
37 return cmp; 37 return cmp;
38} 38}
39 39
40/*----------------------------------------------------------------------------------------------*/
41
40struct Impl_Keys { 42struct Impl_Keys {
41 iSortedArray bindings; 43 iArray bindings;
44 iPtrSet lookup; /* quick key/mods lookup */
42}; 45};
43 46
44static iKeys keys_; 47static iKeys keys_;
45 48
46static void clear_Keys_(iKeys *d) { 49static void clear_Keys_(iKeys *d) {
47 iForEach(Array, i, &d->bindings.values) { 50 iForEach(Array, i, &d->bindings) {
48 iBinding *bind = i.value; 51 iBinding *bind = i.value;
49 deinit_String(&bind->command); 52 deinit_String(&bind->command);
50 deinit_String(&bind->label); 53 deinit_String(&bind->label);
@@ -52,27 +55,41 @@ static void clear_Keys_(iKeys *d) {
52} 55}
53 56
54static void bindDefaults_(void) { 57static void bindDefaults_(void) {
55 bind_Keys("scroll.top", SDLK_HOME, 0); 58 /* TODO: This indirection could be used for localization, although all UI strings
56 bind_Keys("scroll.bottom", SDLK_END, 0); 59 would need to be similarly handled. */
57 bind_Keys("scroll.step arg:-1", SDLK_UP, 0); 60 bindLabel_Keys(1, "scroll.top", SDLK_HOME, 0, "Jump to top");
58 bind_Keys("scroll.step arg:1", SDLK_DOWN, 0); 61 bindLabel_Keys(2, "scroll.bottom", SDLK_END, 0, "Jump to bottom");
59 bind_Keys("scroll.page arg:-1", SDLK_PAGEUP, 0); 62 bindLabel_Keys(10, "scroll.step arg:-1", SDLK_UP, 0, "Scroll up");
60 bind_Keys("scroll.page arg:1", SDLK_PAGEDOWN, 0); 63 bindLabel_Keys(11, "scroll.step arg:1", SDLK_DOWN, 0, "Scroll down");
61 bind_Keys("scroll.page arg:-1", SDLK_SPACE, KMOD_SHIFT); 64 bindLabel_Keys(20, "scroll.page arg:-1", SDLK_PAGEUP, 0, "Scroll up half a page");
62 bind_Keys("scroll.page arg:1", SDLK_SPACE, 0); 65 bindLabel_Keys(21, "scroll.page arg:1", SDLK_PAGEDOWN, 0, "Scroll down half a page");
66 /* The following cannot currently be changed (built-in duplicates). */
67 bind_Keys(1000, "scroll.page arg:-1", SDLK_SPACE, KMOD_SHIFT);
68 bind_Keys(1001, "scroll.page arg:1", SDLK_SPACE, 0);
63} 69}
64 70
65static iBinding *find_Keys_(iKeys *d, int key, int mods) { 71static iBinding *find_Keys_(iKeys *d, int key, int mods) {
66 size_t pos; 72 size_t pos;
67 if (locate_SortedArray(&d->bindings, &(iBinding){ .key = key, .mods = mods }, &pos)) { 73 const iBinding elem = { .key = key, .mods = mods };
68 return at_SortedArray(&d->bindings, pos); 74 if (locate_PtrSet(&d->lookup, &elem, &pos)) {
75 return at_PtrSet(&d->lookup, pos);
76 }
77 return NULL;
78}
79
80static iBinding *findId_Keys_(iKeys *d, int id) {
81 iForEach(Array, i, &d->bindings) {
82 iBinding *bind = i.value;
83 if (bind->id == id) {
84 return bind;
85 }
69 } 86 }
70 return NULL; 87 return NULL;
71} 88}
72 89
73static iBinding *findCommand_Keys_(iKeys *d, const char *command) { 90static iBinding *findCommand_Keys_(iKeys *d, const char *command) {
74 /* Note: O(n) */ 91 /* Note: O(n) */
75 iForEach(Array, i, &d->bindings.values) { 92 iForEach(Array, i, &d->bindings) {
76 iBinding *bind = i.value; 93 iBinding *bind = i.value;
77 if (!cmp_String(&bind->command, command)) { 94 if (!cmp_String(&bind->command, command)) {
78 return bind; 95 return bind;
@@ -81,18 +98,37 @@ static iBinding *findCommand_Keys_(iKeys *d, const char *command) {
81 return NULL; 98 return NULL;
82} 99}
83 100
101static void updateLookup_Keys_(iKeys *d) {
102 clear_PtrSet(&d->lookup);
103 iConstForEach(Array, i, &d->bindings) {
104 insert_PtrSet(&d->lookup, i.value);
105 }
106}
107
108void setKey_Binding(int id, int key, int mods) {
109 iBinding *bind = findId_Keys_(&keys_, id);
110 if (bind) {
111 bind->key = key;
112 bind->mods = mods;
113 updateLookup_Keys_(&keys_);
114 }
115}
116
84/*----------------------------------------------------------------------------------------------*/ 117/*----------------------------------------------------------------------------------------------*/
85 118
86void init_Keys(void) { 119void init_Keys(void) {
87 iKeys *d = &keys_; 120 iKeys *d = &keys_;
88 init_SortedArray(&d->bindings, sizeof(iBinding), cmp_Binding_); 121 init_Array(&d->bindings, sizeof(iBinding));
122 initCmp_PtrSet(&d->lookup, cmpPtr_Binding_);
89 bindDefaults_(); 123 bindDefaults_();
124 updateLookup_Keys_(d);
90} 125}
91 126
92void deinit_Keys(void) { 127void deinit_Keys(void) {
93 iKeys *d = &keys_; 128 iKeys *d = &keys_;
94 clear_Keys_(d); 129 clear_Keys_(d);
95 deinit_SortedArray(&d->bindings); 130 deinit_PtrSet(&d->lookup);
131 deinit_Array(&d->bindings);
96} 132}
97 133
98void load_Keys(const char *saveDir) { 134void load_Keys(const char *saveDir) {
@@ -103,34 +139,42 @@ void save_Keys(const char *saveDir) {
103 139
104} 140}
105 141
106void bind_Keys(const char *command, int key, int mods) { 142void bind_Keys(int id, const char *command, int key, int mods) {
107 iKeys *d = &keys_; 143 iKeys *d = &keys_;
108 iBinding *bind = find_Keys_(d, key, mods); 144 iBinding *bind = findId_Keys_(d, id);
109 if (bind) { 145 if (!bind) {
110 setCStr_String(&bind->command, command); 146 iBinding elem = { .id = id, .key = key, .mods = mods };
147 initCStr_String(&elem.command, command);
148 init_String(&elem.label);
149 pushBack_Array(&d->bindings, &elem);
111 } 150 }
112 else { 151 else {
113 iBinding bind; 152 setCStr_String(&bind->command, command);
114 bind.key = key; 153 bind->key = key;
115 bind.mods = mods; 154 bind->mods = mods;
116 initCStr_String(&bind.command, command);
117 init_String(&bind.label);
118 insert_SortedArray(&d->bindings, &bind);
119 } 155 }
120} 156}
121 157
122void setLabel_Keys(const char *command, const char *label) { 158void setLabel_Keys(int id, const char *label) {
123 iBinding *bind = findCommand_Keys_(&keys_, command); 159 iBinding *bind = findId_Keys_(&keys_, id);
124 if (bind) { 160 if (bind) {
125 setCStr_String(&bind->label, label); 161 setCStr_String(&bind->label, label);
126 } 162 }
127} 163}
128 164
129//const iString *label_Keys(const char *command) { 165#if 0
130 166const iString *label_Keys(const char *command) {
131//} 167 iKeys *d = &keys_;
132 168 /* TODO: A hash wouldn't hurt here. */
133//const char *shortcutLabel_Keys(const char *command) {} 169 iConstForEach(PtrSet, i, &d->bindings) {
170 const iBinding *bind = *i.value;
171 if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {
172 return &bind->label;
173 }
174 }
175 return collectNew_String();
176}
177#endif
134 178
135iBool processEvent_Keys(const SDL_Event *ev) { 179iBool processEvent_Keys(const SDL_Event *ev) {
136 iKeys *d = &keys_; 180 iKeys *d = &keys_;
@@ -147,3 +191,12 @@ iBool processEvent_Keys(const SDL_Event *ev) {
147const iBinding *findCommand_Keys(const char *command) { 191const iBinding *findCommand_Keys(const char *command) {
148 return findCommand_Keys_(&keys_, command); 192 return findCommand_Keys_(&keys_, command);
149} 193}
194
195const iPtrArray *list_Keys(void) {
196 iKeys *d = &keys_;
197 iPtrArray *list = collectNew_PtrArray();
198 iConstForEach(Array, i, &d->bindings) {
199 pushBack_PtrArray(list, i.value);
200 }
201 return list;
202}
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. */
23#pragma once 23#pragma once
24 24
25#include <the_Foundation/string.h> 25#include <the_Foundation/string.h>
26#include <the_Foundation/ptrarray.h>
26#include <SDL_events.h> 27#include <SDL_events.h>
27 28
28#if defined (iPlatformApple) 29#if defined (iPlatformApple)
@@ -46,23 +47,32 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
46iDeclareType(Binding) 47iDeclareType(Binding)
47 48
48struct Impl_Binding { 49struct Impl_Binding {
50 int id;
49 int key; 51 int key;
50 int mods; 52 int mods;
51 iString command; 53 iString command;
52 iString label; 54 iString label;
53}; 55};
54 56
57void setKey_Binding (int id, int key, int mods);
58
59/*----------------------------------------------------------------------------------------------*/
60
55void init_Keys (void); 61void init_Keys (void);
56void deinit_Keys (void); 62void deinit_Keys (void);
57 63
58void load_Keys (const char *saveDir); 64void load_Keys (const char *saveDir);
59void save_Keys (const char *saveDir); 65void save_Keys (const char *saveDir);
60 66
61void bind_Keys (const char *command, int key, int mods); 67void bind_Keys (int id, const char *command, int key, int mods);
62void setLabel_Keys (const char *command, const char *label); 68void setLabel_Keys (int id, const char *label);
63const iBinding *findCommand_Keys (const char *command);
64 69
65//const iString * label_Keys (const char *command); 70iLocalDef void bindLabel_Keys(int id, const char *command, int key, int mods, const char *label) {
66//const char * shortcutLabel_Keys (const char *command); 71 bind_Keys(id, command, key, mods);
72 setLabel_Keys(id, label);
73}
74
75const iBinding *findCommand_Keys (const char *command);
67 76
68iBool processEvent_Keys (const SDL_Event *); 77iBool processEvent_Keys (const SDL_Event *);
78const 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) {
120 const int contentSize = size_PtrArray(&d->items) * d->itemHeight; 120 const int contentSize = size_PtrArray(&d->items) * d->itemHeight;
121 const iRect bounds = innerBounds_Widget(as_Widget(d)); 121 const iRect bounds = innerBounds_Widget(as_Widget(d));
122 const iBool wasVisible = isVisible_Widget(d->scroll); 122 const iBool wasVisible = isVisible_Widget(d->scroll);
123 if (area_Rect(bounds) == 0) {
124 return;
125 }
123 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) }); 126 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) });
124 setThumb_ScrollWidget(d->scroll, 127 setThumb_ScrollWidget(d->scroll,
125 d->scrollY, 128 d->scrollY,
@@ -245,11 +248,21 @@ void updateMouseHover_ListWidget(iListWidget *d) {
245 setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse)); 248 setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse));
246} 249}
247 250
251void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) {
252 sort_Array(&d->items, (iSortedArrayCompareElemFunc) cmp);
253}
254
248static void redrawHoverItem_ListWidget_(iListWidget *d) { 255static void redrawHoverItem_ListWidget_(iListWidget *d) {
249 insert_IntSet(&d->invalidItems, d->hoverItem); 256 insert_IntSet(&d->invalidItems, d->hoverItem);
250 refresh_Widget(as_Widget(d)); 257 refresh_Widget(as_Widget(d));
251} 258}
252 259
260static void sizeChanged_ListWidget_(iListWidget *d) {
261 printf("ListWidget %p size changed: %d x %d\n", d, d->widget.rect.size.x, d->widget.rect.size.y); fflush(stdout);
262 updateVisible_ListWidget(d);
263 invalidate_ListWidget(d);
264}
265
253static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { 266static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
254 iWidget *w = as_Widget(d); 267 iWidget *w = as_Widget(d);
255 if (isCommand_SDLEvent(ev)) { 268 if (isCommand_SDLEvent(ev)) {
@@ -391,4 +404,5 @@ iBool isMouseDown_ListWidget(const iListWidget *d) {
391iBeginDefineSubclass(ListWidget, Widget) 404iBeginDefineSubclass(ListWidget, Widget)
392 .processEvent = (iAny *) processEvent_ListWidget_, 405 .processEvent = (iAny *) processEvent_ListWidget_,
393 .draw = (iAny *) draw_ListWidget_, 406 .draw = (iAny *) draw_ListWidget_,
407 .sizeChanged = (iAny *) sizeChanged_ListWidget_,
394iEndDefineSubclass(ListWidget) 408iEndDefineSubclass(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);
64void updateVisible_ListWidget (iListWidget *); 64void updateVisible_ListWidget (iListWidget *);
65void updateMouseHover_ListWidget (iListWidget *); 65void updateMouseHover_ListWidget (iListWidget *);
66 66
67void sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2));
68
67iAnyObject * item_ListWidget (iListWidget *, size_t index); 69iAnyObject * item_ListWidget (iListWidget *, size_t index);
68iAnyObject * hoverItem_ListWidget (iListWidget *); 70iAnyObject * hoverItem_ListWidget (iListWidget *);
69 71
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)
485 /* Handle commands. */ 485 /* Handle commands. */
486 if (isResize_UserEvent(ev)) { 486 if (isResize_UserEvent(ev)) {
487 checkModeButtonLayout_SidebarWidget_(d); 487 checkModeButtonLayout_SidebarWidget_(d);
488 updateVisible_ListWidget(d->list);
489 invalidate_ListWidget(d->list);
490 } 488 }
491 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { 489 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {
492 const char *cmd = command_UserEvent(ev); 490 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. */
29#include "gmutil.h" 29#include "gmutil.h"
30#include "labelwidget.h" 30#include "labelwidget.h"
31#include "inputwidget.h" 31#include "inputwidget.h"
32#include "bindingswidget.h"
32#include "keys.h" 33#include "keys.h"
33#include "widget.h" 34#include "widget.h"
34#include "text.h" 35#include "text.h"
@@ -105,6 +106,11 @@ void toString_Sym(int key, int kmods, iString *str) {
105 } 106 }
106} 107}
107 108
109iBool isMod_Sym(int key) {
110 return key == SDLK_LALT || key == SDLK_RALT || key == SDLK_LCTRL || key == SDLK_RCTRL ||
111 key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;
112}
113
108int keyMods_Sym(int kmods) { 114int keyMods_Sym(int kmods) {
109 kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI); 115 kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);
110 /* Don't treat left/right modifiers differently. */ 116 /* Don't treat left/right modifiers differently. */
@@ -920,12 +926,22 @@ iWidget *makeToggle_Widget(const char *id) {
920 return toggle; 926 return toggle;
921} 927}
922 928
929static void appendFramelessTabPage_(iWidget *tabs, iWidget *page, const char *title, int shortcut,
930 int kmods) {
931 appendTabPage_Widget(tabs, page, title, shortcut, kmods);
932 setFlags_Widget(
933 (iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),
934 frameless_WidgetFlag,
935 iTrue);
936}
937
923static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int shortcut, iWidget **headings, 938static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int shortcut, iWidget **headings,
924 iWidget **values) { 939 iWidget **values) {
925 iWidget *page = new_Widget(); 940 iWidget *page = new_Widget();
926 setFlags_Widget(page, arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag | 941 setFlags_Widget(page, arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |
927 resizeHeightOfChildren_WidgetFlag | borderTop_WidgetFlag, iTrue); 942 resizeHeightOfChildren_WidgetFlag | borderTop_WidgetFlag, iTrue);
928 addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag); 943 addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);
944 setPadding_Widget(page, 0, gap_UI, 0, gap_UI);
929 iWidget *columns = new_Widget(); 945 iWidget *columns = new_Widget();
930 addChildFlags_Widget(page, iClob(columns), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 946 addChildFlags_Widget(page, iClob(columns), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
931 *headings = addChildFlags_Widget( 947 *headings = addChildFlags_Widget(
@@ -933,11 +949,7 @@ static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int short
933 *values = addChildFlags_Widget( 949 *values = addChildFlags_Widget(
934 columns, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); 950 columns, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
935 addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag); 951 addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);
936 appendTabPage_Widget(tabs, page, title, shortcut, shortcut ? KMOD_PRIMARY : 0); 952 appendFramelessTabPage_(tabs, page, title, shortcut, shortcut ? KMOD_PRIMARY : 0);
937 setFlags_Widget(
938 (iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),
939 frameless_WidgetFlag,
940 iTrue);
941 return page; 953 return page;
942} 954}
943 955
@@ -1080,6 +1092,11 @@ iWidget *makePreferences_Widget(void) {
1080 addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:"))); 1092 addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:")));
1081 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http"); 1093 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http");
1082 } 1094 }
1095 /* Keybindings. */ {
1096 iBindingsWidget *bind = new_BindingsWidget();
1097 setFlags_Widget(as_Widget(bind), borderTop_WidgetFlag, iTrue);
1098 appendFramelessTabPage_(tabs, iClob(bind), "Bindings", '5', KMOD_PRIMARY);
1099 }
1083 resizeToLargestPage_Widget(tabs); 1100 resizeToLargestPage_Widget(tabs);
1084 arrange_Widget(dlg); 1101 arrange_Widget(dlg);
1085 /* Set input field sizes. */ { 1102 /* 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) {
48# define KMOD_SECONDARY KMOD_GUI 48# define KMOD_SECONDARY KMOD_GUI
49#endif 49#endif
50 50
51iBool isMod_Sym (int key);
51int keyMods_Sym (int kmods); /* shift, alt, control, or gui */ 52int keyMods_Sym (int kmods); /* shift, alt, control, or gui */
52void toString_Sym (int key, int kmods, iString *str); 53void toString_Sym (int key, int kmods, iString *str);
53 54