aboutsummaryrefslogtreecommitdiff
path: root/src/FbTk/Menu.cc
diff options
context:
space:
mode:
authormarkt <markt>2007-03-03 19:35:34 (GMT)
committermarkt <markt>2007-03-03 19:35:34 (GMT)
commita233229bd854d2e925ca0f1e86846ff9fde46fcd (patch)
treea30fffa38994e8ee12096c31a256ba6b3fbfa2c6 /src/FbTk/Menu.cc
parentd6a7bd786fd657e16c1ebad5c515d60ba1368d8a (diff)
downloadfluxbox_paul-a233229bd854d2e925ca0f1e86846ff9fde46fcd.zip
fluxbox_paul-a233229bd854d2e925ca0f1e86846ff9fde46fcd.tar.bz2
added support for typeahead in menus
Diffstat (limited to 'src/FbTk/Menu.cc')
-rw-r--r--src/FbTk/Menu.cc195
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
207int Menu::insert(MenuItem *item, int pos) { 208int 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
222void Menu::fixMenuItemIndices() {
223 for (size_t i = 0; i < menuitems.size(); i++)
224 menuitems[i]->setIndex(i);
225}
226
217int Menu::remove(unsigned int index) { 227int 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
259void Menu::removeAll() { 272void 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
266void Menu::raise() { 277void Menu::raise() {
@@ -271,55 +282,48 @@ void Menu::lower() {
271 menu.window.lower(); 282 menu.window.lower();
272} 283}
273 284
274void Menu::nextItem(int failsafe) { 285void 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
310void 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
345void Menu::enterSubmenu() { 337void 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
362void Menu::enterParent() { 354void 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)
1154void Menu::clearItem(int index, bool clear) { 1171void 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
1236void 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
1246void 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
1257void 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