diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 1 | ||||
-rw-r--r-- | src/ui/bindingswidget.c | 193 | ||||
-rw-r--r-- | src/ui/bindingswidget.h | 28 | ||||
-rw-r--r-- | src/ui/keys.c | 123 | ||||
-rw-r--r-- | src/ui/keys.h | 20 | ||||
-rw-r--r-- | src/ui/listwidget.c | 14 | ||||
-rw-r--r-- | src/ui/listwidget.h | 2 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 2 | ||||
-rw-r--r-- | src/ui/util.c | 27 | ||||
-rw-r--r-- | src/ui/util.h | 1 |
10 files changed, 364 insertions, 47 deletions
@@ -430,6 +430,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
430 | static void deinit_App(iApp *d) { | 430 | static 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 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. 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 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY 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 | ||
21 | SOFTWARE, 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 | |||
30 | iDeclareType(BindingItem) | ||
31 | typedef iListItemClass iBindingItemClass; | ||
32 | |||
33 | struct Impl_BindingItem { | ||
34 | iListItem listItem; | ||
35 | iString label; | ||
36 | iString key; | ||
37 | int id; | ||
38 | iBool isWaitingForEvent; | ||
39 | }; | ||
40 | |||
41 | void 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 | |||
49 | void deinit_BindingItem(iBindingItem *d) { | ||
50 | deinit_String(&d->key); | ||
51 | deinit_String(&d->label); | ||
52 | } | ||
53 | |||
54 | static 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 | |||
60 | static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect, | ||
61 | const iListWidget *list); | ||
62 | |||
63 | iBeginDefineSubclass(BindingItem, ListItem) | ||
64 | .draw = (iAny *) draw_BindingItem_, | ||
65 | iEndDefineSubclass(BindingItem) | ||
66 | |||
67 | iDefineObjectConstruction(BindingItem) | ||
68 | |||
69 | /*----------------------------------------------------------------------------------------------*/ | ||
70 | |||
71 | struct Impl_BindingsWidget { | ||
72 | iWidget widget; | ||
73 | iListWidget *list; | ||
74 | size_t activePos; | ||
75 | }; | ||
76 | |||
77 | iDefineObjectConstruction(BindingsWidget) | ||
78 | |||
79 | static 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 | |||
84 | static 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 | |||
103 | void 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 | |||
114 | void deinit_BindingsWidget(iBindingsWidget *d) { | ||
115 | /* nothing to do */ | ||
116 | iUnused(d); | ||
117 | } | ||
118 | |||
119 | static 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 | |||
133 | static 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 | |||
154 | static 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 | |||
160 | static 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 | |||
190 | iBeginDefineSubclass(BindingsWidget, Widget) | ||
191 | .processEvent = (iAny *) processEvent_BindingsWidget_, | ||
192 | .draw = (iAny *) draw_BindingsWidget_, | ||
193 | 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 @@ | |||
1 | /* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. 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 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY 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 | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #pragma once | ||
24 | |||
25 | #include "widget.h" | ||
26 | |||
27 | iDeclareWidgetClass(BindingsWidget) | ||
28 | 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. */ | |||
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 | ||
29 | iDeclareType(Keys) | 29 | iDeclareType(Keys) |
30 | 30 | ||
31 | static int cmp_Binding_(const void *a, const void *b) { | 31 | static 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 | |||
40 | struct Impl_Keys { | 42 | struct Impl_Keys { |
41 | iSortedArray bindings; | 43 | iArray bindings; |
44 | iPtrSet lookup; /* quick key/mods lookup */ | ||
42 | }; | 45 | }; |
43 | 46 | ||
44 | static iKeys keys_; | 47 | static iKeys keys_; |
45 | 48 | ||
46 | static void clear_Keys_(iKeys *d) { | 49 | static 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 | ||
54 | static void bindDefaults_(void) { | 57 | static 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 | ||
65 | static iBinding *find_Keys_(iKeys *d, int key, int mods) { | 71 | static 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 | |||
80 | static 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 | ||
73 | static iBinding *findCommand_Keys_(iKeys *d, const char *command) { | 90 | static 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 | ||
101 | static 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 | |||
108 | void 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 | ||
86 | void init_Keys(void) { | 119 | void 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 | ||
92 | void deinit_Keys(void) { | 127 | void 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 | ||
98 | void load_Keys(const char *saveDir) { | 134 | void load_Keys(const char *saveDir) { |
@@ -103,34 +139,42 @@ void save_Keys(const char *saveDir) { | |||
103 | 139 | ||
104 | } | 140 | } |
105 | 141 | ||
106 | void bind_Keys(const char *command, int key, int mods) { | 142 | void 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 | ||
122 | void setLabel_Keys(const char *command, const char *label) { | 158 | void 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 | 166 | const 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 | ||
135 | iBool processEvent_Keys(const SDL_Event *ev) { | 179 | iBool 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) { | |||
147 | const iBinding *findCommand_Keys(const char *command) { | 191 | const iBinding *findCommand_Keys(const char *command) { |
148 | return findCommand_Keys_(&keys_, command); | 192 | return findCommand_Keys_(&keys_, command); |
149 | } | 193 | } |
194 | |||
195 | const 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. */ | |||
46 | iDeclareType(Binding) | 47 | iDeclareType(Binding) |
47 | 48 | ||
48 | struct Impl_Binding { | 49 | struct 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 | ||
57 | void setKey_Binding (int id, int key, int mods); | ||
58 | |||
59 | /*----------------------------------------------------------------------------------------------*/ | ||
60 | |||
55 | void init_Keys (void); | 61 | void init_Keys (void); |
56 | void deinit_Keys (void); | 62 | void deinit_Keys (void); |
57 | 63 | ||
58 | void load_Keys (const char *saveDir); | 64 | void load_Keys (const char *saveDir); |
59 | void save_Keys (const char *saveDir); | 65 | void save_Keys (const char *saveDir); |
60 | 66 | ||
61 | void bind_Keys (const char *command, int key, int mods); | 67 | void bind_Keys (int id, const char *command, int key, int mods); |
62 | void setLabel_Keys (const char *command, const char *label); | 68 | void setLabel_Keys (int id, const char *label); |
63 | const iBinding *findCommand_Keys (const char *command); | ||
64 | 69 | ||
65 | //const iString * label_Keys (const char *command); | 70 | iLocalDef 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 | |||
75 | const iBinding *findCommand_Keys (const char *command); | ||
67 | 76 | ||
68 | iBool processEvent_Keys (const SDL_Event *); | 77 | iBool processEvent_Keys (const SDL_Event *); |
78 | 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) { | |||
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 | ||
251 | void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) { | ||
252 | sort_Array(&d->items, (iSortedArrayCompareElemFunc) cmp); | ||
253 | } | ||
254 | |||
248 | static void redrawHoverItem_ListWidget_(iListWidget *d) { | 255 | static 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 | ||
260 | static 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 | |||
253 | static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | 266 | static 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) { | |||
391 | iBeginDefineSubclass(ListWidget, Widget) | 404 | iBeginDefineSubclass(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_, | ||
394 | iEndDefineSubclass(ListWidget) | 408 | 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); | |||
64 | void updateVisible_ListWidget (iListWidget *); | 64 | void updateVisible_ListWidget (iListWidget *); |
65 | void updateMouseHover_ListWidget (iListWidget *); | 65 | void updateMouseHover_ListWidget (iListWidget *); |
66 | 66 | ||
67 | void sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2)); | ||
68 | |||
67 | iAnyObject * item_ListWidget (iListWidget *, size_t index); | 69 | iAnyObject * item_ListWidget (iListWidget *, size_t index); |
68 | iAnyObject * hoverItem_ListWidget (iListWidget *); | 70 | iAnyObject * 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 | ||
109 | iBool 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 | |||
108 | int keyMods_Sym(int kmods) { | 114 | int 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 | ||
929 | static 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 | |||
923 | static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int shortcut, iWidget **headings, | 938 | static 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 | ||
51 | iBool isMod_Sym (int key); | ||
51 | int keyMods_Sym (int kmods); /* shift, alt, control, or gui */ | 52 | int keyMods_Sym (int kmods); /* shift, alt, control, or gui */ |
52 | void toString_Sym (int key, int kmods, iString *str); | 53 | void toString_Sym (int key, int kmods, iString *str); |
53 | 54 | ||