diff options
author | markt <markt> | 2007-03-03 19:35:34 (GMT) |
---|---|---|
committer | markt <markt> | 2007-03-03 19:35:34 (GMT) |
commit | a233229bd854d2e925ca0f1e86846ff9fde46fcd (patch) | |
tree | a30fffa38994e8ee12096c31a256ba6b3fbfa2c6 /src/FbTk/Menu.cc | |
parent | d6a7bd786fd657e16c1ebad5c515d60ba1368d8a (diff) | |
download | fluxbox_pavel-a233229bd854d2e925ca0f1e86846ff9fde46fcd.zip fluxbox_pavel-a233229bd854d2e925ca0f1e86846ff9fde46fcd.tar.bz2 |
added support for typeahead in menus
Diffstat (limited to 'src/FbTk/Menu.cc')
-rw-r--r-- | src/FbTk/Menu.cc | 195 |
1 files changed, 127 insertions, 68 deletions
diff --git a/src/FbTk/Menu.cc b/src/FbTk/Menu.cc index e72b750..c60286a 100644 --- a/src/FbTk/Menu.cc +++ b/src/FbTk/Menu.cc | |||
@@ -110,6 +110,7 @@ Menu::Menu(MenuTheme &tm, ImageControl &imgctrl): | |||
110 | m_visible = false; | 110 | m_visible = false; |
111 | 111 | ||
112 | 112 | ||
113 | m_type_ahead.init(menuitems); | ||
113 | 114 | ||
114 | menu.x_move = | 115 | menu.x_move = |
115 | menu.y_move = 0; | 116 | menu.y_move = 0; |
@@ -205,15 +206,24 @@ int Menu::insert(const FbString &label, Menu *submenu, int pos) { | |||
205 | } | 206 | } |
206 | 207 | ||
207 | int Menu::insert(MenuItem *item, int pos) { | 208 | int Menu::insert(MenuItem *item, int pos) { |
209 | if (item == 0) | ||
210 | return menuitems.size(); | ||
208 | if (pos == -1) { | 211 | if (pos == -1) { |
212 | item->setIndex(menuitems.size()); | ||
209 | menuitems.push_back(item); | 213 | menuitems.push_back(item); |
210 | } else { | 214 | } else { |
211 | menuitems.insert(menuitems.begin() + pos, item); | 215 | menuitems.insert(menuitems.begin() + pos, item); |
216 | fixMenuItemIndices(); | ||
212 | } | 217 | } |
213 | m_need_update = true; // we need to redraw the menu | 218 | m_need_update = true; // we need to redraw the menu |
214 | return menuitems.size(); | 219 | return menuitems.size(); |
215 | } | 220 | } |
216 | 221 | ||
222 | void Menu::fixMenuItemIndices() { | ||
223 | for (size_t i = 0; i < menuitems.size(); i++) | ||
224 | menuitems[i]->setIndex(i); | ||
225 | } | ||
226 | |||
217 | int Menu::remove(unsigned int index) { | 227 | int Menu::remove(unsigned int index) { |
218 | if (index >= menuitems.size()) { | 228 | if (index >= menuitems.size()) { |
219 | #ifdef DEBUG | 229 | #ifdef DEBUG |
@@ -229,6 +239,9 @@ int Menu::remove(unsigned int index) { | |||
229 | 239 | ||
230 | if (item) { | 240 | if (item) { |
231 | menuitems.erase(it); | 241 | menuitems.erase(it); |
242 | // avoid O(n^2) algorithm with removeAll() | ||
243 | if (index != menuitems.size()) | ||
244 | fixMenuItemIndices(); | ||
232 | 245 | ||
233 | if (item->submenu() != 0) { | 246 | if (item->submenu() != 0) { |
234 | Menu *tmp = item->submenu(); | 247 | Menu *tmp = item->submenu(); |
@@ -257,10 +270,8 @@ int Menu::remove(unsigned int index) { | |||
257 | } | 270 | } |
258 | 271 | ||
259 | void Menu::removeAll() { | 272 | void Menu::removeAll() { |
260 | while (!menuitems.empty()) { | 273 | while (!menuitems.empty()) |
261 | remove(0); | 274 | remove(menuitems.size()-1); |
262 | } | ||
263 | m_need_update = true; | ||
264 | } | 275 | } |
265 | 276 | ||
266 | void Menu::raise() { | 277 | void Menu::raise() { |
@@ -271,55 +282,48 @@ void Menu::lower() { | |||
271 | menu.window.lower(); | 282 | menu.window.lower(); |
272 | } | 283 | } |
273 | 284 | ||
274 | void Menu::nextItem(int failsafe) { | 285 | void Menu::cycleItems(bool reverse) { |
275 | if (menuitems.empty()) | 286 | Menuitems vec; |
276 | return; | 287 | if (m_type_ahead.stringSize()) |
277 | 288 | vec = m_matches; | |
278 | if (failsafe == -1) | 289 | else |
279 | failsafe = m_active_index; | 290 | vec = menuitems; |
280 | 291 | ||
281 | int old_active_index = m_active_index; | 292 | if (vec.size() < 1) |
282 | m_active_index += 1; | 293 | return; |
283 | if (!validIndex(m_active_index)) | ||
284 | m_active_index = 0; | ||
285 | 294 | ||
286 | if (validIndex(old_active_index) && | 295 | // find the next item to select |
287 | menuitems[old_active_index] != 0) { | 296 | // this algorithm assumes menuitems are sorted properly |
288 | if (menuitems[old_active_index]->submenu()) { | 297 | int new_index = -1; |
289 | // we need to do this explicitly on the menu.window | 298 | bool passed = !validIndex(m_active_index); |
290 | // since it might hide the parent if we use Menu::hide | 299 | for (size_t i = 0; i < vec.size(); i++) { |
291 | menuitems[old_active_index]->submenu()->internal_hide(); | 300 | if (!isItemSelectable(vec[i]->getIndex()) || |
301 | vec[i]->getIndex() == m_active_index) | ||
302 | continue; | ||
303 | |||
304 | // determine whether or not we've passed the active index | ||
305 | if (!passed && vec[i]->getIndex() > m_active_index) { | ||
306 | if (reverse && new_index != -1) | ||
307 | break; | ||
308 | passed = true; | ||
292 | } | 309 | } |
293 | clearItem(old_active_index); | ||
294 | } | ||
295 | 310 | ||
296 | if (menuitems[m_active_index] == 0) { | 311 | // decide if we want to keep this item |
297 | m_active_index = -1; | 312 | if (passed && !reverse) { |
298 | return; | 313 | new_index = vec[i]->getIndex(); |
299 | } | 314 | break; |
300 | 315 | } else if (reverse || new_index == -1) | |
301 | if (!isItemSelectable(m_active_index) && m_active_index != failsafe) { | 316 | new_index = vec[i]->getIndex(); |
302 | nextItem(failsafe); | ||
303 | return; | ||
304 | } | 317 | } |
305 | 318 | ||
306 | clearItem(m_active_index); | 319 | if (new_index == -1) |
307 | |||
308 | } | ||
309 | |||
310 | void Menu::prevItem(int failsafe) { | ||
311 | if (menuitems.empty()) | ||
312 | return; | 320 | return; |
313 | 321 | ||
314 | if (failsafe == -1) | 322 | // clear the items and close any open submenus |
315 | failsafe = m_active_index; | ||
316 | |||
317 | int old_active_index = m_active_index; | 323 | int old_active_index = m_active_index; |
318 | m_active_index -= 1; | 324 | m_active_index = new_index; |
319 | if (!validIndex(m_active_index)) | 325 | if (validIndex(old_active_index) && |
320 | m_active_index = menuitems.size() - 1; | 326 | menuitems[old_active_index] != 0) { |
321 | |||
322 | if (validIndex(old_active_index)) { | ||
323 | if (menuitems[old_active_index]->submenu()) { | 327 | if (menuitems[old_active_index]->submenu()) { |
324 | // we need to do this explicitly on the menu.window | 328 | // we need to do this explicitly on the menu.window |
325 | // since it might hide the parent if we use Menu::hide | 329 | // since it might hide the parent if we use Menu::hide |
@@ -327,19 +331,7 @@ void Menu::prevItem(int failsafe) { | |||
327 | } | 331 | } |
328 | clearItem(old_active_index); | 332 | clearItem(old_active_index); |
329 | } | 333 | } |
330 | 334 | clearItem(new_index); | |
331 | if (menuitems[m_active_index] == 0) { | ||
332 | m_active_index = -1; | ||
333 | return; | ||
334 | } | ||
335 | |||
336 | if (!isItemSelectable(m_active_index) && m_active_index != failsafe) { | ||
337 | prevItem(failsafe); | ||
338 | return; | ||
339 | } | ||
340 | |||
341 | clearItem(m_active_index); | ||
342 | |||
343 | } | 335 | } |
344 | 336 | ||
345 | void Menu::enterSubmenu() { | 337 | void Menu::enterSubmenu() { |
@@ -356,7 +348,7 @@ void Menu::enterSubmenu() { | |||
356 | drawSubmenu(m_active_index); | 348 | drawSubmenu(m_active_index); |
357 | submenu->grabInputFocus(); | 349 | submenu->grabInputFocus(); |
358 | submenu->m_active_index = -1; // so we land on 0 after nextItem() | 350 | submenu->m_active_index = -1; // so we land on 0 after nextItem() |
359 | submenu->nextItem(); | 351 | submenu->cycleItems(false); |
360 | } | 352 | } |
361 | 353 | ||
362 | void Menu::enterParent() { | 354 | void Menu::enterParent() { |
@@ -1024,34 +1016,59 @@ void Menu::keyPressEvent(XKeyEvent &event) { | |||
1024 | 1016 | ||
1025 | switch (ks) { | 1017 | switch (ks) { |
1026 | case XK_Up: | 1018 | case XK_Up: |
1027 | prevItem(); | 1019 | resetTypeAhead(); |
1020 | cycleItems(true); | ||
1028 | break; | 1021 | break; |
1029 | case XK_Down: | 1022 | case XK_Down: |
1030 | nextItem(); | 1023 | resetTypeAhead(); |
1024 | cycleItems(false); | ||
1031 | break; | 1025 | break; |
1032 | case XK_Left: // enter parent if we have one | 1026 | case XK_Left: // enter parent if we have one |
1027 | resetTypeAhead(); | ||
1033 | enterParent(); | 1028 | enterParent(); |
1034 | break; | 1029 | break; |
1035 | case XK_Right: // enter submenu if we have one | 1030 | case XK_Right: // enter submenu if we have one |
1031 | resetTypeAhead(); | ||
1036 | enterSubmenu(); | 1032 | enterSubmenu(); |
1037 | break; | 1033 | break; |
1038 | case XK_Escape: // close menu | 1034 | case XK_Escape: // close menu |
1035 | m_type_ahead.reset(); | ||
1039 | hide(); | 1036 | hide(); |
1040 | break; | 1037 | break; |
1038 | case XK_BackSpace: | ||
1039 | m_type_ahead.putBackSpace(); | ||
1040 | drawTypeAheadItems(); | ||
1041 | break; | ||
1041 | case XK_KP_Enter: | 1042 | case XK_KP_Enter: |
1042 | case XK_Return: | 1043 | case XK_Return: |
1043 | // send fake button 1 click | 1044 | resetTypeAhead(); |
1044 | if (validIndex(m_active_index) && | 1045 | if (validIndex(m_active_index) && |
1045 | isItemEnabled(m_active_index)) { | 1046 | isItemEnabled(m_active_index)) { |
1046 | if (event.state & ShiftMask) | 1047 | if (menuitems[m_active_index]->submenu() != 0) |
1047 | menuitems[m_active_index]->click(3, event.time); | 1048 | enterSubmenu(); |
1048 | else | 1049 | else { |
1049 | menuitems[m_active_index]->click(1, event.time); | 1050 | // send fake button click |
1050 | m_need_update = true; | 1051 | int button = (event.state & ShiftMask) ? 3 : 1; |
1051 | updateMenu(); | 1052 | find(m_active_index)->click(button, event.time); |
1053 | m_need_update = true; | ||
1054 | updateMenu(); | ||
1055 | } | ||
1052 | } | 1056 | } |
1053 | break; | 1057 | break; |
1058 | case XK_Tab: | ||
1059 | case XK_ISO_Left_Tab: | ||
1060 | m_type_ahead.seek(); | ||
1061 | cycleItems((bool)(event.state & ShiftMask)); | ||
1062 | drawTypeAheadItems(); | ||
1063 | break; | ||
1054 | default: | 1064 | default: |
1065 | m_type_ahead.putCharacter(keychar[0]); | ||
1066 | // if current item doesn't match new search string, find the next one | ||
1067 | drawTypeAheadItems(); | ||
1068 | if (!m_matches.empty() && (!validIndex(m_active_index) || | ||
1069 | std::find(m_matches.begin(), m_matches.end(), | ||
1070 | find(m_active_index)) == m_matches.end())) | ||
1071 | cycleItems(false); | ||
1055 | break; | 1072 | break; |
1056 | } | 1073 | } |
1057 | } | 1074 | } |
@@ -1151,7 +1168,7 @@ void Menu::renderForeground(FbWindow &win, FbDrawable &drawable) { | |||
1151 | // thus sometimes it won't perform the actual clear operation | 1168 | // thus sometimes it won't perform the actual clear operation |
1152 | // nothing in here should be rendered transparently | 1169 | // nothing in here should be rendered transparently |
1153 | // (unless you use a caching pixmap, which I think we should avoid) | 1170 | // (unless you use a caching pixmap, which I think we should avoid) |
1154 | void Menu::clearItem(int index, bool clear) { | 1171 | void Menu::clearItem(int index, bool clear, int search_index) { |
1155 | if (!validIndex(index)) | 1172 | if (!validIndex(index)) |
1156 | return; | 1173 | return; |
1157 | 1174 | ||
@@ -1160,9 +1177,16 @@ void Menu::clearItem(int index, bool clear) { | |||
1160 | int item_x = (sbl * item_w), item_y = (i * item_h); | 1177 | int item_x = (sbl * item_w), item_y = (i * item_h); |
1161 | bool highlight = (index == m_active_index && isItemSelectable(index)); | 1178 | bool highlight = (index == m_active_index && isItemSelectable(index)); |
1162 | 1179 | ||
1180 | if (search_index < 0) | ||
1181 | // find if we need to underline the item | ||
1182 | search_index = std::find(m_matches.begin(), m_matches.end(), | ||
1183 | find(index)) - m_matches.begin(); | ||
1184 | |||
1163 | // don't highlight if moving, doesn't work with alpha on | 1185 | // don't highlight if moving, doesn't work with alpha on |
1164 | if (highlight && !m_moving) { | 1186 | if (highlight && !m_moving) { |
1165 | highlightItem(index); | 1187 | highlightItem(index); |
1188 | if (search_index < (int)m_matches.size()) | ||
1189 | drawLine(index, m_type_ahead.stringSize()); | ||
1166 | return; | 1190 | return; |
1167 | } else if (clear) | 1191 | } else if (clear) |
1168 | menu.frame.clearArea(item_x, item_y, item_w, item_h); | 1192 | menu.frame.clearArea(item_x, item_y, item_w, item_h); |
@@ -1173,6 +1197,9 @@ void Menu::clearItem(int index, bool clear) { | |||
1173 | item->draw(menu.frame, theme(), highlight, | 1197 | item->draw(menu.frame, theme(), highlight, |
1174 | true, false, item_x, item_y, | 1198 | true, false, item_x, item_y, |
1175 | item_w, item_h); | 1199 | item_w, item_h); |
1200 | |||
1201 | if (search_index < (int)m_matches.size()) | ||
1202 | drawLine(index, m_type_ahead.stringSize()); | ||
1176 | } | 1203 | } |
1177 | 1204 | ||
1178 | // Area must have been cleared before calling highlight | 1205 | // Area must have been cleared before calling highlight |
@@ -1206,4 +1233,36 @@ void Menu::highlightItem(int index) { | |||
1206 | 1233 | ||
1207 | } | 1234 | } |
1208 | 1235 | ||
1236 | void Menu::resetTypeAhead() { | ||
1237 | Menuitems vec = m_matches; | ||
1238 | Menuitems::iterator it = vec.begin(); | ||
1239 | m_type_ahead.reset(); | ||
1240 | m_matches.clear(); | ||
1241 | |||
1242 | for (; it != vec.end(); it++) | ||
1243 | clearItem((*it)->getIndex(), true, 1); | ||
1244 | } | ||
1245 | |||
1246 | void Menu::drawTypeAheadItems() { | ||
1247 | // remove underlines from old matches | ||
1248 | for (size_t i = 0; i < m_matches.size(); i++) | ||
1249 | clearItem(m_matches[i]->getIndex(), true, m_matches.size()); | ||
1250 | |||
1251 | m_matches = m_type_ahead.matched(); | ||
1252 | for (size_t j = 0; j < m_matches.size(); j++) | ||
1253 | clearItem(m_matches[j]->getIndex(), false, j); | ||
1254 | } | ||
1255 | |||
1256 | // underline menuitem[index] with respect to matchstringsize size | ||
1257 | void Menu::drawLine(int index, int size){ | ||
1258 | if (!validIndex(index)) | ||
1259 | return; | ||
1260 | |||
1261 | int sbl = index / menu.persub, i = index - (sbl * menu.persub); | ||
1262 | int item_x = (sbl * menu.item_w), item_y = (i * theme().itemHeight()); | ||
1263 | |||
1264 | FbTk::MenuItem *item = find(index); | ||
1265 | item->drawLine(menu.frame, theme(), size, item_x, item_y, menu.item_w); | ||
1266 | } | ||
1267 | |||
1209 | }; // end namespace FbTk | 1268 | }; // end namespace FbTk |