added support for typeahead in menus
@@ -1,5 +1,12 @@
(Format: Year/Month/Day) Changes for 1.0rc3: +*07/03/03 + * Added typeahead support to menus (Mathias, Mark + thanks Matteo Galiazzo) + - Added new style item menu.frame.underlineColor: <color> for displaying + matching items + FbTk/Menu.cc/hh MenuItem.cc/hh MenuTheme.cc/hh Makefile.am and added + FbTk/ITypeAheadable.hh TypeAhead.hh SearchResult.cc/hh +*07/03/02 * Added support for keypad enter key in menu (Mark) FbTk/Menu.cc *07/02/28:
@@ -0,0 +1,49 @@
+// ITypeAheadable.hh for FbTk - Fluxbox Toolkit +// Copyright (c) 2007 Fluxbox Team (fluxgen at fluxbox dot org) +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#ifndef FBTK_ITYPEAHEADABLE_HH +#define FBTK_ITYPEAHEADABLE_HH + +#include <string> + +namespace FbTk { + +// abstract base class providing access and validation +class ITypeAheadable { +public: + virtual ~ITypeAheadable() { } + + virtual const std::string &iTypeString() const = 0; + virtual bool isEnabled() { return true; } + char iTypeChar(size_t i) const { return iTypeString()[i]; } + bool iTypeCheckStringSize(size_t sz) const { + return (iTypeString().size() > sz); + } + bool iTypeCompareChar(char ch, size_t sz) const { + return (bool)iTypeCheckStringSize(sz) && + tolower(iTypeChar(sz)) == tolower(ch); + } + +}; + +} // end namespace FbTk + +#endif // FBTK_ITYPEAHEADABLE_HH
@@ -51,6 +51,7 @@ KeyUtil.hh KeyUtil.cc \
MenuSeparator.hh MenuSeparator.cc \ MenuIcon.hh MenuIcon.cc \ stringstream.hh \ + TypeAhead.hh SearchResult.hh SearchResult.cc ITypeAheadable.hh \ Select2nd.hh \ CachedPixmap.hh CachedPixmap.cc \ ${xpm_SOURCE} \
@@ -110,6 +110,7 @@ m_torn =
m_visible = false; + m_type_ahead.init(menuitems); menu.x_move = menu.y_move = 0;@@ -205,15 +206,24 @@ return insert(new MenuItem(label, submenu), pos);
} int Menu::insert(MenuItem *item, int pos) { + if (item == 0) + return menuitems.size(); if (pos == -1) { + item->setIndex(menuitems.size()); menuitems.push_back(item); } else { menuitems.insert(menuitems.begin() + pos, item); + fixMenuItemIndices(); } m_need_update = true; // we need to redraw the menu return menuitems.size(); } +void Menu::fixMenuItemIndices() { + for (size_t i = 0; i < menuitems.size(); i++) + menuitems[i]->setIndex(i); +} + int Menu::remove(unsigned int index) { if (index >= menuitems.size()) { #ifdef DEBUG@@ -229,6 +239,9 @@ MenuItem *item = (*it);
if (item) { menuitems.erase(it); + // avoid O(n^2) algorithm with removeAll() + if (index != menuitems.size()) + fixMenuItemIndices(); if (item->submenu() != 0) { Menu *tmp = item->submenu();@@ -257,10 +270,8 @@ return menuitems.size();
} void Menu::removeAll() { - while (!menuitems.empty()) { - remove(0); - } - m_need_update = true; + while (!menuitems.empty()) + remove(menuitems.size()-1); } void Menu::raise() {@@ -271,55 +282,48 @@ void Menu::lower() {
menu.window.lower(); } -void Menu::nextItem(int failsafe) { - if (menuitems.empty()) +void Menu::cycleItems(bool reverse) { + Menuitems vec; + if (m_type_ahead.stringSize()) + vec = m_matches; + else + vec = menuitems; + + if (vec.size() < 1) return; - if (failsafe == -1) - failsafe = m_active_index; - - int old_active_index = m_active_index; - m_active_index += 1; - if (!validIndex(m_active_index)) - m_active_index = 0; + // find the next item to select + // this algorithm assumes menuitems are sorted properly + int new_index = -1; + bool passed = !validIndex(m_active_index); + for (size_t i = 0; i < vec.size(); i++) { + if (!isItemSelectable(vec[i]->getIndex()) || + vec[i]->getIndex() == m_active_index) + continue; - if (validIndex(old_active_index) && - menuitems[old_active_index] != 0) { - if (menuitems[old_active_index]->submenu()) { - // we need to do this explicitly on the menu.window - // since it might hide the parent if we use Menu::hide - menuitems[old_active_index]->submenu()->internal_hide(); + // determine whether or not we've passed the active index + if (!passed && vec[i]->getIndex() > m_active_index) { + if (reverse && new_index != -1) + break; + passed = true; } - clearItem(old_active_index); - } - if (menuitems[m_active_index] == 0) { - m_active_index = -1; - return; + // decide if we want to keep this item + if (passed && !reverse) { + new_index = vec[i]->getIndex(); + break; + } else if (reverse || new_index == -1) + new_index = vec[i]->getIndex(); } - if (!isItemSelectable(m_active_index) && m_active_index != failsafe) { - nextItem(failsafe); + if (new_index == -1) return; - } - clearItem(m_active_index); - -} - -void Menu::prevItem(int failsafe) { - if (menuitems.empty()) - return; - - if (failsafe == -1) - failsafe = m_active_index; - + // clear the items and close any open submenus int old_active_index = m_active_index; - m_active_index -= 1; - if (!validIndex(m_active_index)) - m_active_index = menuitems.size() - 1; - - if (validIndex(old_active_index)) { + m_active_index = new_index; + if (validIndex(old_active_index) && + menuitems[old_active_index] != 0) { if (menuitems[old_active_index]->submenu()) { // we need to do this explicitly on the menu.window // since it might hide the parent if we use Menu::hide@@ -327,19 +331,7 @@ menuitems[old_active_index]->submenu()->internal_hide();
} clearItem(old_active_index); } - - if (menuitems[m_active_index] == 0) { - m_active_index = -1; - return; - } - - if (!isItemSelectable(m_active_index) && m_active_index != failsafe) { - prevItem(failsafe); - return; - } - - clearItem(m_active_index); - + clearItem(new_index); } void Menu::enterSubmenu() {@@ -356,7 +348,7 @@
drawSubmenu(m_active_index); submenu->grabInputFocus(); submenu->m_active_index = -1; // so we land on 0 after nextItem() - submenu->nextItem(); + submenu->cycleItems(false); } void Menu::enterParent() {@@ -1024,34 +1016,59 @@ return;
switch (ks) { case XK_Up: - prevItem(); + resetTypeAhead(); + cycleItems(true); break; case XK_Down: - nextItem(); + resetTypeAhead(); + cycleItems(false); break; case XK_Left: // enter parent if we have one + resetTypeAhead(); enterParent(); break; case XK_Right: // enter submenu if we have one + resetTypeAhead(); enterSubmenu(); break; case XK_Escape: // close menu + m_type_ahead.reset(); hide(); break; + case XK_BackSpace: + m_type_ahead.putBackSpace(); + drawTypeAheadItems(); + break; case XK_KP_Enter: case XK_Return: - // send fake button 1 click + resetTypeAhead(); if (validIndex(m_active_index) && isItemEnabled(m_active_index)) { - if (event.state & ShiftMask) - menuitems[m_active_index]->click(3, event.time); - else - menuitems[m_active_index]->click(1, event.time); - m_need_update = true; - updateMenu(); + if (menuitems[m_active_index]->submenu() != 0) + enterSubmenu(); + else { + // send fake button click + int button = (event.state & ShiftMask) ? 3 : 1; + find(m_active_index)->click(button, event.time); + m_need_update = true; + updateMenu(); + } } break; + case XK_Tab: + case XK_ISO_Left_Tab: + m_type_ahead.seek(); + cycleItems((bool)(event.state & ShiftMask)); + drawTypeAheadItems(); + break; default: + m_type_ahead.putCharacter(keychar[0]); + // if current item doesn't match new search string, find the next one + drawTypeAheadItems(); + if (!m_matches.empty() && (!validIndex(m_active_index) || + std::find(m_matches.begin(), m_matches.end(), + find(m_active_index)) == m_matches.end())) + cycleItems(false); break; } }@@ -1151,7 +1168,7 @@ // clear item clears the item and draws the dynamic bits
// thus sometimes it won't perform the actual clear operation // nothing in here should be rendered transparently // (unless you use a caching pixmap, which I think we should avoid) -void Menu::clearItem(int index, bool clear) { +void Menu::clearItem(int index, bool clear, int search_index) { if (!validIndex(index)) return;@@ -1159,10 +1176,17 @@ int sbl = index / menu.persub, i = index - (sbl * menu.persub);
unsigned int item_w = menu.item_w, item_h = theme().itemHeight(); int item_x = (sbl * item_w), item_y = (i * item_h); bool highlight = (index == m_active_index && isItemSelectable(index)); + + if (search_index < 0) + // find if we need to underline the item + search_index = std::find(m_matches.begin(), m_matches.end(), + find(index)) - m_matches.begin(); // don't highlight if moving, doesn't work with alpha on if (highlight && !m_moving) { highlightItem(index); + if (search_index < (int)m_matches.size()) + drawLine(index, m_type_ahead.stringSize()); return; } else if (clear) menu.frame.clearArea(item_x, item_y, item_w, item_h);@@ -1173,6 +1197,9 @@
item->draw(menu.frame, theme(), highlight, true, false, item_x, item_y, item_w, item_h); + + if (search_index < (int)m_matches.size()) + drawLine(index, m_type_ahead.stringSize()); } // Area must have been cleared before calling highlight@@ -1204,6 +1231,38 @@ 0, 0,
item_x, item_y, item_w, item_h); +} + +void Menu::resetTypeAhead() { + Menuitems vec = m_matches; + Menuitems::iterator it = vec.begin(); + m_type_ahead.reset(); + m_matches.clear(); + + for (; it != vec.end(); it++) + clearItem((*it)->getIndex(), true, 1); +} + +void Menu::drawTypeAheadItems() { + // remove underlines from old matches + for (size_t i = 0; i < m_matches.size(); i++) + clearItem(m_matches[i]->getIndex(), true, m_matches.size()); + + m_matches = m_type_ahead.matched(); + for (size_t j = 0; j < m_matches.size(); j++) + clearItem(m_matches[j]->getIndex(), false, j); +} + +// underline menuitem[index] with respect to matchstringsize size +void Menu::drawLine(int index, int size){ + if (!validIndex(index)) + return; + + int sbl = index / menu.persub, i = index - (sbl * menu.persub); + int item_x = (sbl * menu.item_w), item_y = (i * theme().itemHeight()); + + FbTk::MenuItem *item = find(index); + item->drawLine(menu.frame, theme(), size, item_x, item_y, menu.item_w); } }; // end namespace FbTk
@@ -41,6 +41,7 @@ #include "FbPixmap.hh"
#include "MenuTheme.hh" #include "Timer.hh" #include "FbString.hh" +#include "TypeAhead.hh" namespace FbTk {@@ -87,10 +88,8 @@ /// raise this window
virtual void raise(); /// lower this window virtual void lower(); - /// select next item - void nextItem(int failsafe = -1); - /// select previous item - void prevItem(int failsafe = -1); + /// cycle through menuitems + void cycleItems(bool reverse); void enterSubmenu(); void enterParent();@@ -186,7 +185,7 @@ // renders item onto pm
int drawItem(FbDrawable &pm, unsigned int index, bool highlight = false, bool exclusive_drawable = false); - void clearItem(int index, bool clear = true); + void clearItem(int index, bool clear = true, int search_index = -1); void highlightItem(int index); virtual void redrawTitle(FbDrawable &pm); virtual void redrawFrame(FbDrawable &pm);@@ -208,6 +207,14 @@ MenuTheme &m_theme;
Menu *m_parent; ImageControl &m_image_ctrl; Menuitems menuitems; + + TypeAhead<Menuitems, MenuItem *> m_type_ahead; + Menuitems m_matches; + + void resetTypeAhead(); + void drawTypeAheadItems(); + void drawLine(int index, int size); + void fixMenuItemIndices(); int m_screen_x, m_screen_y; unsigned int m_screen_width, m_screen_height;
@@ -38,6 +38,44 @@ if (m_command.get() != 0)
m_command->execute(); } +void MenuItem::drawLine(FbDrawable &draw, const MenuTheme &theme, size_t size, + int text_x, int text_y, unsigned int width) const { + + unsigned int height = theme.itemHeight(); + int bevelW = theme.bevelWidth(); + + int font_top = (height - theme.frameFont().height())/2; + int underline_height = font_top + theme.frameFont().ascent() + 2; + int bottom = height - bevelW - 1; + + text_y += bottom > underline_height ? underline_height : bottom; + int text_w = theme.frameFont().textWidth(m_label.c_str(), m_label.size()); + + // width of the searchstring + size = size > m_label.length() ? m_label.length() : size; + std::string search_string = m_label.substr(0,size); + int search_string_w = theme.frameFont().textWidth(search_string.c_str(), size); + + // pay attention to the text justification + switch(theme.frameFontJustify()) { + case FbTk::LEFT: + text_x += bevelW + height + 1; + break; + case FbTk::RIGHT: + text_x += width - (height + bevelW + text_w); + break; + default: //center + text_x += ((width + 1 - text_w) / 2); + break; + } + + // avoid drawing an ugly dot + if (size != 0) + draw.drawLine(theme.frameUnderlineGC().gc(), + text_x, text_y, text_x + search_string_w, text_y); + +} + void MenuItem::draw(FbDrawable &draw, const MenuTheme &theme, bool highlight, bool draw_foreground, bool draw_background,
@@ -27,6 +27,7 @@
#include "RefCount.hh" #include "Command.hh" #include "PixmapWithMask.hh" +#include "ITypeAheadable.hh" #include "FbString.hh" #include <string>@@ -39,7 +40,7 @@ class MenuTheme;
class FbDrawable; /// An interface for a menu item in Menu -class MenuItem { +class MenuItem : public FbTk::ITypeAheadable { public: MenuItem() : m_label(""),@@ -105,6 +106,17 @@ virtual const Menu *submenu() const { return m_submenu; }
virtual bool isEnabled() const { return m_enabled; } virtual bool isSelected() const { return m_selected; } virtual bool isToggleItem() const { return m_toggle_item; } + + // iType functions + virtual inline void setIndex(int index) { m_index = index; } + virtual inline int getIndex() { return m_index; } + inline const std::string &iTypeString() const { return m_label; } + virtual void drawLine(FbDrawable &draw, + const MenuTheme &theme, + size_t size, + int text_x, int text_y, + unsigned int width) const; + virtual unsigned int width(const MenuTheme &theme) const; virtual unsigned int height(const MenuTheme &theme) const; virtual void draw(FbDrawable &drawable,@@ -137,6 +149,7 @@ Menu *m_submenu; ///< a submenu, 0 if we don't have one
RefCount<Command> m_command; ///< command to be executed bool m_enabled, m_selected; bool m_toggle_item; + int m_index; struct Icon { std::auto_ptr<PixmapWithMask> pixmap;
@@ -44,6 +44,7 @@ t_text(*this, "menu.title.textColor", "Menu.Title.TextColor"),
f_text(*this, "menu.frame.textColor", "Menu.Frame.TextColor"), h_text(*this, "menu.hilite.textColor", "Menu.Hilite.TextColor"), d_text(*this, "menu.frame.disableColor", "Menu.Frame.DisableColor"), + u_text(*this, "menu.frame.underlineColor", "Menu.Frame.UnderlineColor"), title(*this, "menu.title", "Menu.Title"), frame(*this, "menu.frame", "Menu.Frame"), hilite(*this, "menu.hilite", "Menu.Hilite"),@@ -67,6 +68,7 @@ m_hl_unselected_pixmap(*this, "menu.hilite.unselected.pixmap", "Menu.Hilite.Unselected.Pixmap"),
m_display(FbTk::App::instance()->display()), t_text_gc(RootWindow(m_display, screen_num)), f_text_gc(RootWindow(m_display, screen_num)), + u_text_gc(RootWindow(m_display, screen_num)), h_text_gc(RootWindow(m_display, screen_num)), d_text_gc(RootWindow(m_display, screen_num)), hilite_gc(RootWindow(m_display, screen_num)),@@ -91,6 +93,7 @@ *m_border_width = 0;
t_text_gc.setForeground(*t_text); f_text_gc.setForeground(*f_text); + u_text_gc.setForeground(*u_text); h_text_gc.setForeground(*h_text); d_text_gc.setForeground(*d_text); hilite_gc.setForeground(hilite->color());@@ -127,6 +130,7 @@ m_hl_unselected_pixmap->scale(item_pm_height, item_pm_height);
t_text_gc.setForeground(*t_text); f_text_gc.setForeground(*f_text); + u_text_gc.setForeground(*u_text); h_text_gc.setForeground(*h_text); d_text_gc.setForeground(*d_text); hilite_gc.setForeground(hilite->color());
@@ -56,6 +56,7 @@ */
///@{ inline const FbTk::Color &titleTextColor() const { return *t_text; } inline const FbTk::Color &frameTextColor() const { return *f_text; } + inline const FbTk::Color &frameUnderlineColor() const { return *u_text; } inline const FbTk::Color &highlightTextColor() const { return *h_text; } inline const FbTk::Color &disableTextColor() const { return *d_text; } ///@}@@ -94,11 +95,13 @@ */
///@{ inline const GContext &titleTextGC() const { return t_text_gc; } inline const GContext &frameTextGC() const { return f_text_gc; } + inline const GContext &frameUnderlineGC() const { return u_text_gc; } inline const GContext &hiliteTextGC() const { return h_text_gc; } inline const GContext &disableTextGC() const { return d_text_gc; } inline const GContext &hiliteGC() const { return hilite_gc; } inline GContext &titleTextGC() { return t_text_gc; } inline GContext &frameTextGC() { return f_text_gc; } + inline GContext &frameUnderlineGC() { return u_text_gc; } inline GContext &hiliteTextGC() { return h_text_gc; } inline GContext &disableTextGC() { return d_text_gc; } inline GContext &hiliteGC() { return hilite_gc; }@@ -139,7 +142,7 @@ m_hl_selected_pixmap->pixmap().dontFree();
} private: - FbTk::ThemeItem<FbTk::Color> t_text, f_text, h_text, d_text; + FbTk::ThemeItem<FbTk::Color> t_text, f_text, h_text, d_text, u_text; FbTk::ThemeItem<FbTk::Texture> title, frame, hilite; FbTk::ThemeItem<FbTk::Font> titlefont, framefont; FbTk::ThemeItem<FbTk::Justify> framefont_justify, titlefont_justify;@@ -153,7 +156,7 @@ FbTk::ThemeItem<FbTk::PixmapWithMask> m_bullet_pixmap, m_selected_pixmap, m_unselected_pixmap;
FbTk::ThemeItem<FbTk::PixmapWithMask> m_hl_bullet_pixmap, m_hl_selected_pixmap, m_hl_unselected_pixmap; Display *m_display; - FbTk::GContext t_text_gc, f_text_gc, h_text_gc, d_text_gc, hilite_gc; + FbTk::GContext t_text_gc, f_text_gc, u_text_gc, h_text_gc, d_text_gc, hilite_gc; unsigned char m_alpha; MenuMode m_menumode;
@@ -0,0 +1,51 @@
+// SearchResult.cc for FbTk - Fluxbox Toolkit +// Copyright (c) 2007 Fluxbox Team (fluxgen at fluxbox dot org) +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#include "SearchResult.hh" +#include <vector> + +namespace FbTk { + +void SearchResult::seek() { + switch (m_results.size()) { + case 0: + break; + case 1: + m_seeked_string = m_results[0]->iTypeString(); + break; + default: + bool seekforward = true; + for (size_t i=1; i < m_results.size() && seekforward && + m_results[0]->iTypeCheckStringSize(m_seeked_string.size()); i++) { + if (!m_results[i]->iTypeCompareChar( + m_results[0]->iTypeChar(m_seeked_string.size()), + m_seeked_string.size())) { + seekforward = false; + } else if (i == m_results.size() - 1) { + m_seeked_string += m_results[0]->iTypeChar(m_seeked_string.size()); + i = 0; + } + } + break; + } +} + +} // end namespace FbTk
@@ -0,0 +1,53 @@
+// SearchResult.hh for FbTk - Fluxbox Toolkit +// Copyright (c) 2007 Fluxbox Team (fluxgen at fluxbox dot org) +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#ifndef FBTK_SEARCHRESULT_HH +#define FBTK_SEARCHRESULT_HH + +#include <vector> +#include "ITypeAheadable.hh" + +namespace FbTk { + +class SearchResult { +public: + typedef std::vector < ITypeAheadable* > BaseItems; + typedef BaseItems::iterator BaseItemsIt; + + SearchResult(const std::string &to_search_for): + m_seeked_string(to_search_for) { } + + void add(ITypeAheadable* item) { m_results.push_back(item); } + size_t size() const { return m_results.size(); } + const BaseItems& result() const { return m_results; } + const std::string& seekedString() const { return m_seeked_string; } + + void seek(); + +private: + BaseItems m_results; + std::string m_seeked_string; + +}; + +} // end namespace FbTk + +#endif // FBTK_SEARCHRESULT_HH
@@ -0,0 +1,174 @@
+// TypeAhead.hh for FbTk - Fluxbox Toolkit +// Copyright (c) 2007 Fluxbox Team (fluxgen at fluxbox dot org) +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#ifndef FBTK_TYPEAHEAD_HH +#define FBTK_TYPEAHEAD_HH + +#include "ITypeAheadable.hh" +#include <vector> +#include "SearchResult.hh" + +namespace FbTk { + +template <typename Items, typename Item_Type> +class TypeAhead { +/* + +a class template can't be split into separate interface + implementation files, an interface summary is given here: + +public: + void init(Items const &items); + +// accessors: + inline int stringSize() const { return m_searchstr.size(); } + Items matched() const; + +// modifiers: + Items putCharacter(char ch); + void putBackSpace(); + void reset() + +private: + SearchResults m_search_results; + std::string m_searchstr; + Items const *m_ref; + +// helper + void fillValues(BaseItems const &search, ValueVec &fillin) const; + +// reverts to searchstate before current + void revert(); + +// search performs iteration and sets state + void search(char char_to_test); + void doSearch(char to_test, + Items const &items, + SearchResult &mySearchResult) const; + void doSearch(char to_test, + BaseItems const &search, + SearchResult &mySearchResult) const; +*/ + +public: + typedef std::vector < ITypeAheadable* > BaseItems; + typedef BaseItems::const_iterator BaseItemscIt; + typedef std::vector < SearchResult > SearchResults; + typedef typename Items::const_iterator ItemscIt; + + void init(Items const &items) { m_ref = &items; } + + inline size_t stringSize() const { return m_searchstr.size(); } + + void seek() { + if (!m_search_results.empty()) + m_searchstr = m_search_results.back().seekedString(); + } + + Items putCharacter(char ch) { + if (isprint(ch)) + search(ch); + return matched(); + } + + void putBackSpace() { + if (!m_search_results.empty()) + revert(); + } + + void reset() { + m_searchstr.clear(); + m_search_results.clear(); + } + + Items matched() const { + Items last_matched; + + if (!m_search_results.empty()) + fillValues(m_search_results.back().result(), last_matched); + return last_matched; + } + +private: + SearchResults m_search_results; + std::string m_searchstr; + Items const *m_ref; // reference to vector we are operating on + + void fillValues(BaseItems const &search, Items &fillin) const { + for (BaseItemscIt it = search.begin(); it != search.end(); it++) { + Item_Type tmp = dynamic_cast<Item_Type>(*it); + if (tmp) + fillin.push_back(tmp); + } + } + + void revert() { + m_search_results.pop_back(); + if (m_search_results.empty()) + m_searchstr.clear(); + else + m_searchstr = m_search_results.back().seekedString(); + } + + void search(char char_to_test) { + SearchResult mySearchResult(m_searchstr + char_to_test); + size_t num_items = m_ref->size(); + + // check if we have already a searched set + if (m_search_results.empty()) + doSearch(char_to_test, *m_ref, mySearchResult); + else { + num_items = m_search_results.back().size(); + doSearch(char_to_test, m_search_results.back().result(), + mySearchResult); + } + + if (mySearchResult.size() > 0 ) { + if (mySearchResult.size() < num_items) { + mySearchResult.seek(); + m_search_results.push_back(mySearchResult); + } + m_searchstr += char_to_test; + } + } + + // iteration based on original list of items + void doSearch(char to_test, Items const &items, + SearchResult &mySearchResult) const { + for (ItemscIt it = items.begin(); it != items.end(); it++) { + if ((*it)->iTypeCompareChar(to_test, stringSize()) && (*it)->isEnabled()) + mySearchResult.add(*it); + } + } + + // iteration based on last SearchResult + void doSearch(char to_test, BaseItems const &search, + SearchResult &mySearchResult) const { + for (BaseItemscIt it = search.begin(); it != search.end(); it++) { + if ((*it)->iTypeCompareChar(to_test, stringSize()) && (*it)->isEnabled()) + mySearchResult.add(*it); + } + } + +}; // end Class TypeAhead + +} // end namespace FbTk + +#endif // FBTK_TYPEAHEAD_HH