# # # menu_typeahead.patch adds typeahead feature to fluxbox menus # # Menu behaviour in general changed, return enters a submenu or starts an application. # Backspace enters the parent menu the same way as cursor left does. # In typeahead 'mode' that changes a little bit, backspace enters parent if and only if there is no 'pattern match state' to revert to. # Tab iterates through the matches downwards, shift tab upwards. # Cursor keys reset any typeahead activities. # # The goal is to provide a fast and intuitive menu navigation with visual feedback, if you experience situations where typeahead does something very different from what you expected, please tell me. # A short description for typeahead: imagine yourself pressing tab after each character you enter in a term, thats exactly what you get. The addon is visual feedback by underlining, backspace key reverting the searchstate, tab and shift tab to browse. # any suggestions, critics, bugs let me know... i'm around in #fluxbox on Freenode.net irc network aka Sin` or contact me by email # # patch tested and worked for svn revision 3998. # # Added resources: # # ~/.fluxbox/init: # added useTypeahead: true|false # # style files: # added menu.frame.underline.Color: color # added menu.frame.underline.Justify: Left|Center|Right # # usage of the patch: # download to /somedir # cp /somedir/menu_typeahead.patch /path/to/fluxbox_directory/ # cd /path/to/fluxbox_directory/ # patch -p0 < menu_typeahead.patch # # proceed with ./autogen.sh; ./configure; make... # # have fun! # # patch by philipp goedl, email: philipp.goedl [at] stuwo [dot] at # Index: src/FbMenu.hh =================================================================== --- src/FbMenu.hh (Revision 3997) +++ src/FbMenu.hh (Arbeitskopie) @@ -26,4 +26,6 @@ #include "Menu.hh" +#include "MenuItem.hh" +#include "FbTk/TypeAhead.hh" #include "XLayerItem.hh" #include @@ -34,4 +36,7 @@ class FbMenu:public FbTk::Menu { public: + typedef std::vector ValueVec; + typedef ValueVec::iterator ValueVecIt; + FbMenu(MenuTheme &tm, FbTk::ImageControl &imgctrl, FbTk::XLayer &layer); @@ -43,6 +48,13 @@ void reconfigure(); private: + + FbTk::TypeAhead m_type_ahead; FbTk::XLayerItem m_layeritem; std::auto_ptr m_shape; + void clearTypeAheadItems(); + void clearTypeAheadItems(ValueVec vec); + void drawTypeAheadItems(); + void keyPressEvent(XKeyEvent &event); + void drawLine(int index, int size); }; Index: src/fluxbox.hh =================================================================== --- src/fluxbox.hh (Revision 3997) +++ src/fluxbox.hh (Arbeitskopie) @@ -155,4 +155,5 @@ inline time_t getAutoRaiseDelay() const { return *m_rc_auto_raise_delay; } + inline bool getUseTypeahead() const { return *m_rc_menu_type_ahead; } inline unsigned int getCacheLife() const { return *m_rc_cache_life * 60000; } @@ -272,4 +273,5 @@ FbTk::Resource m_rc_cache_life, m_rc_cache_max; FbTk::Resource m_rc_auto_raise_delay; + FbTk::Resource m_rc_menu_type_ahead; FbTk::Resource m_rc_use_mod1; /// temporary!, to disable mod1 for resize/move Index: src/FbMenu.cc =================================================================== --- src/FbMenu.cc (Revision 3997) +++ src/FbMenu.cc (Arbeitskopie) @@ -25,13 +25,18 @@ #include "FbMenu.hh" #include "MenuTheme.hh" - +#include "fluxbox.hh" #include "Shape.hh" +#include +#include + FbMenu::FbMenu(MenuTheme &tm, FbTk::ImageControl &imgctrl, FbTk::XLayer &layer): FbTk::Menu(tm, imgctrl), m_layeritem(fbwindow(), layer), - m_shape(new Shape(fbwindow(), tm.shapePlaces())) { - + m_shape(new Shape(fbwindow(), tm.shapePlaces())){ + if (Fluxbox::instance()->getUseTypeahead()){ + m_type_ahead.init(-1, Items()); + } } @@ -55,2 +60,141 @@ } +void FbMenu::keyPressEvent(XKeyEvent &event) { + + if (! Fluxbox::instance()->getUseTypeahead()){ + // call Menu's keyPressEvent if we don't use TypeAhead + FbTk::Menu::keyPressEvent(event); + return; + } + + + KeySym ks; + char keychar[1]; + XLookupString(&event, keychar, 1, &ks, 0); + + if (IsModifierKey(ks)) + return; + + switch (ks) { + case XK_Up: + clearTypeAheadItems(); + m_type_ahead.reset(); + FbTk::Menu::keyPressEvent(event); + break; + case XK_Down: + clearTypeAheadItems(); + m_type_ahead.reset(); + FbTk::Menu::keyPressEvent(event); + break; + case XK_Left: // enter parent if we have one + clearTypeAheadItems(); + m_type_ahead.reset(); + FbTk::Menu::keyPressEvent(event); + break; + case XK_Right: // enter submenu if we have one + clearTypeAheadItems(); + m_type_ahead.reset(); + FbTk::Menu::keyPressEvent(event); + break; + case XK_Escape: // close menu + m_type_ahead.reset(); + FbTk::Menu::keyPressEvent(event); + break; + case XK_Return: + clearTypeAheadItems(); + m_type_ahead.reset(); + FbTk::Menu::keyPressEvent(event); + break; + + // XK_BackSpace does not pass the event to Menu, + // this avoids entering the parent menu if typeahead is active + case XK_BackSpace: + clearTypeAheadItems(); + if (m_type_ahead.getValue() == -1){ + m_type_ahead.reset(); + enterParent(); + } else { + jumpToItem(m_type_ahead.putBackSpace()); + drawTypeAheadItems(); + } + break; + case XK_Tab: + { + int old_index = m_type_ahead.getValue(); + jumpToItem(m_type_ahead.next()); + drawLine(old_index, m_type_ahead.searchSize()); + } + FbTk::Menu::keyPressEvent(event); + break; + case XK_ISO_Left_Tab: + { + int old_index = m_type_ahead.getValue(); + jumpToItem(m_type_ahead.prev()); + drawLine(old_index, m_type_ahead.searchSize()); + } + FbTk::Menu::keyPressEvent(event); + break; + default: + if ( !m_type_ahead.isInitialized() ){ + // TODO: look out for a better place to set indexes + for (int i=0; i< numberOfItems(); i++){ + find(i)->setIndex(i); + } + m_type_ahead.init(); + } + + jumpToItem(m_type_ahead.putCharacter(keychar[0])); + drawTypeAheadItems(); + FbTk::Menu::keyPressEvent(event); + break; + } +} + +// clear all lines from menu +void FbMenu::clearTypeAheadItems() { + + ValueVec vec = m_type_ahead.matched(); + + for ( ValueVecIt it= vec.begin(); it != vec.end(); it++){ + clearItem(*it); + } +} + +// clear lines representing old typeahead state +void FbMenu::clearTypeAheadItems(ValueVec vec) { + + for ( ValueVecIt it= vec.begin(); it != vec.end(); it++){ + clearItem(*it); + } +} + +// update lines to current typeahead state +void FbMenu::drawTypeAheadItems() { + + ValueVec vec = m_type_ahead.matched(); + int size = m_type_ahead.searchSize(); + + // clear and draw new matches + for ( ValueVecIt it= vec.begin(); it != vec.end(); it++){ + clearItem(*it); + drawLine(*it, size); + } + + // clear last matches which are now unmatched + clearTypeAheadItems(m_type_ahead.unMatched()); +} + +// underline menuitem[index] with respect to matchstringsize size +void FbMenu::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); +} Index: src/FbTk/MenuItem.hh =================================================================== --- src/FbTk/MenuItem.hh (Revision 3997) +++ src/FbTk/MenuItem.hh (Arbeitskopie) @@ -28,4 +28,5 @@ #include "Command.hh" #include "PixmapWithMask.hh" +#include "ITypeAheadable.hh" #include @@ -39,5 +40,5 @@ /// An interface for a menu item in Menu -class MenuItem { +class MenuItem : public FbTk::ITypeAheadable { public: MenuItem() @@ -75,7 +76,6 @@ m_enabled(true), m_selected(false), - m_toggle_item(false) { - - } + m_toggle_item(false) + { } MenuItem(const char *label, Menu *submenu, Menu *host_menu = 0) @@ -93,7 +93,12 @@ virtual inline void setEnabled(bool enabled) { m_enabled = enabled; } virtual inline void setLabel(const char *label) { m_label = (label ? label : ""); } + virtual inline void setIndex(int index){ m_index = index; } virtual inline void setToggleItem(bool val) { m_toggle_item = val; } void setIcon(const std::string &filename, int screen_num); Menu *submenu() { return m_submenu; } + // iType functions + inline bool iTypeCondition() const { return (iTypeString().size() > 0); } + inline const std::string &iTypeString() const { return m_label; } + inline int iTypeValue() const { return m_index; } /** @name accessors @@ -114,4 +119,9 @@ int x, int y, unsigned int width, unsigned int height) const; + virtual void drawLine(FbDrawable &draw, + const MenuTheme &theme, + int size, + int text_x, int text_y, + unsigned int width) const; virtual void updateTheme(const MenuTheme &theme); /** @@ -135,4 +145,5 @@ bool m_enabled, m_selected; bool m_toggle_item; + int m_index; struct Icon { Index: src/FbTk/Menu.cc =================================================================== --- src/FbTk/Menu.cc (Revision 3997) +++ src/FbTk/Menu.cc (Arbeitskopie) @@ -382,4 +382,19 @@ } +void Menu::pressReturn(XKeyEvent &event){ + // send fake button 1 click + if (validIndex(m_which_press) && + isItemEnabled(m_which_press)) { + if (menuitems[m_which_press]->submenu() != 0) + enterSubmenu(); + else { + menuitems[m_which_press]->click(1, event.time); + itemSelected(1, m_which_press); + m_need_update = true; + updateMenu(); + } + } +} + void Menu::disableTitle() { setTitleVisibility(false); @@ -1135,13 +1150,9 @@ hide(); break; + case XK_BackSpace: + enterParent(); + break; case XK_Return: - // send fake button 1 click - if (validIndex(m_which_press) && - isItemEnabled(m_which_press)) { - menuitems[m_which_press]->click(1, event.time); - itemSelected(1, m_which_press); - m_need_update = true; - updateMenu(); - } + pressReturn(event); break; default: @@ -1293,3 +1304,12 @@ } +void Menu::jumpToItem(int index){ + if (!validIndex(index)) + return; + + m_active_index = -1; + clearItem(m_which_press); + m_active_index = m_which_press = index; + clearItem(index); +} }; // end namespace FbTk Index: src/FbTk/TypeAhead.hh =================================================================== --- src/FbTk/TypeAhead.hh (Revision 0) +++ src/FbTk/TypeAhead.hh (Revision 0) @@ -0,0 +1,259 @@ +#ifndef FBTK_TYPEAHEAD_HH +#define FBTK_TYPEAHEAD_HH + +#include "ITypeAheadable.hh" +#include +#include "SearchResult.hh" + +namespace FbTk { + +template +class TypeAhead { +/* + +a class template can't be splittet into seperate interface + implementation files, an interface summary is given here: + +public: + + void init(); + void init(Value_Type defaultsel, Items const &items); + +// accessors: + int searchSize() const { return m_searchstr.size(); } + bool isInitialized() const { return m_isInitialized; } + ValueVec matched() const; + ValueVec unMatched() const; + Value_Type getValue() const; + +// modifiers: + Value_Type putCharacter(char ch); + Value_Type putBackSpace(); + Value_Type prev(); + Value_Type next(); + void reset() + +private: + + SearchResults m_search_results; + std::string m_searchstr; + Value_Type m_no_selection; + ITypeAheadable *m_selected; + Items const *m_ref; + bool m_isInitialized; + +// 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 iterateMenuItems(char to_test, + Items const &items, + SearchResult &mySearchResult) const; + void iterateSearchResults(char to_test, + BaseItems const &search, + SearchResult &mySearchResult) const; +*/ + +public: + + typedef std::vector < ITypeAheadable_Base* > BaseItems; + typedef BaseItems::iterator BaseItemsIt; + typedef BaseItems::const_iterator BaseItemscIt; + typedef std::vector < SearchResult > SearchResults; + typedef typename std::vector < Vector_Type> Items; + typedef typename Items::iterator ItemsIt; + typedef typename Items::const_iterator ItemscIt; + typedef typename std::vector < Value_Type > ValueVec; + + TypeAhead() : + m_isInitialized(false), + m_selected(0) + {} + + int searchSize() const { return m_searchstr.size(); } + bool isInitialized() const { return m_isInitialized; } + + // workaround to set init state after check for MenuItems indexes set + void init() { + m_isInitialized = true; + } + + void init(Value_Type defaultsel, Items const &items) { + m_no_selection = defaultsel; + m_ref = &items; + m_isInitialized = false; + } + + + Value_Type putCharacter(char ch) { + if (isprint(ch)) { + search(ch); + } + return getValue(); + } + + Value_Type putBackSpace() { + if ( m_isInitialized == true ){ + switch (m_search_results.size()){ + case 0: + break; + case 1: + reset(); + break; + default: + revert(); + break; + } + } + return getValue(); + } + + Value_Type prev(){ + if ( m_search_results.size() > 0){ + m_selected = (ITypeAheadable *) m_search_results[m_search_results.size()-1].prev(); + } + return getValue(); + } + + Value_Type next(){ + if ( m_search_results.size() > 0){ + m_selected = (ITypeAheadable *) m_search_results[m_search_results.size()-1].next(); + } + return getValue(); + } + + void reset() { + m_searchstr.clear(); + m_search_results.clear(); + m_selected = 0; + } + + ValueVec matched() const { + ValueVec last_matched; + + switch (m_search_results.size()){ + case 0: + return last_matched; + break; + case 1: + fillValues(m_search_results[0].result(), last_matched); + last_matched.erase(find(last_matched.begin(), last_matched.end(), m_selected->iTypeValue())); + return last_matched; + break; + default: + int size = m_search_results.size(); + fillValues(m_search_results[m_search_results.size()-1].result(), last_matched); + last_matched.erase(find(last_matched.begin(), last_matched.end(), m_selected->iTypeValue())); + return last_matched; + break; + } + } + + ValueVec unMatched() const { + ValueVec last_un_matched; + + if (m_search_results.size() > 1){ + int last = m_search_results.size()-1; + fillValues(m_search_results[last-1].unique(m_search_results[last]), last_un_matched); + } + + return last_un_matched; + } + + // get the index for menu + Value_Type getValue() const { + return (m_selected == 0)? m_no_selection : m_selected->iTypeValue(); + } + +private: + + SearchResults m_search_results; + std::string m_searchstr; + Value_Type m_no_selection; + ITypeAheadable *m_selected; + Items const *m_ref; // reference to vector we are operating on + bool m_isInitialized; + + void fillValues(BaseItems const &search, ValueVec &fillin) const { + for (BaseItemscIt it = search.begin(); it != search.end(); it++){ + fillin.push_back( ((ITypeAheadable *) *it)->iTypeValue() ); + } + } + + void revert(){ + + m_search_results.pop_back(); + m_searchstr = m_search_results[m_search_results.size()-1].seekedString(); + m_selected = (ITypeAheadable *) m_search_results[m_search_results.size()-1].selected(); + } + + void search(char char_to_test) { + + int sz_char = m_searchstr.size(); + + SearchResult mySearchResult(m_searchstr + char_to_test); + + if ( m_search_results.size() > 0 ) { + + iterateSearchResults(char_to_test, + m_search_results[m_search_results.size()-1].result(), + mySearchResult); + + } else { + + iterateMenuItems(char_to_test, + *m_ref, + mySearchResult); + } + + mySearchResult.seekAndSelect(); + + if ( mySearchResult.size() > 0 ){ + m_search_results.push_back(mySearchResult); + m_searchstr = mySearchResult.seekedString(); + m_selected = (ITypeAheadable *) mySearchResult.selected(); + } + } + + // iteration based on all possible MenuItems + void iterateMenuItems(char to_test, + Items const &items, + SearchResult &mySearchResult) const { + + int sz_char = m_searchstr.size(); + + for (ItemscIt it = items.begin(); it != items.end(); it++){ + if ( (*it)->iTypeCondition() + && (*it)->iTypeCompareChar(to_test, sz_char) ) { + mySearchResult.add(*it); + } + + } + + } + + // iteration based on last SearchResult + void iterateSearchResults(char to_test, + BaseItems const &search, + SearchResult &mySearchResult) const{ + + int sz_char = m_searchstr.size(); + + for (BaseItemscIt it = search.begin(); it != search.end(); it++){ + if ( (*it)->iTypeCompareChar(to_test, sz_char)){ + mySearchResult.add(*it); + if ( *it == m_selected ) { + mySearchResult.setSelected(m_selected); + } + } + } + } + +}; // end Class TypeAhead + +} // end namespace FbTk + +#endif // FBTK_TYPEAHEAD_HH Index: src/FbTk/MenuTheme.cc =================================================================== --- src/FbTk/MenuTheme.cc (Revision 3997) +++ src/FbTk/MenuTheme.cc (Arbeitskopie) @@ -43,4 +43,5 @@ t_text(*this, "menu.title.textColor", "Menu.Title.TextColor"), f_text(*this, "menu.frame.textColor", "Menu.Frame.TextColor"), + u_text(*this, "menu.frame.underline.Color", "Menu.Frame.underline.Color"), h_text(*this, "menu.hilite.textColor", "Menu.Hilite.TextColor"), d_text(*this, "menu.frame.disableColor", "Menu.Frame.DisableColor"), @@ -52,4 +53,5 @@ framefont_justify(*this, "menu.frame.justify", "Menu.Frame.Justify"), titlefont_justify(*this, "menu.title.justify", "Menu.Title.Justify"), + underline_justify(*this, "menu.frame.underline.justify", "Menu.Frame.Underline.Justify"), bullet_pos(*this, "menu.bullet.position", "Menu.Bullet.Position"), m_bullet(*this, "menu.bullet", "Menu.Bullet"), @@ -68,4 +70,5 @@ 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)), @@ -92,4 +95,5 @@ 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); @@ -128,4 +132,5 @@ 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); Index: src/FbTk/MenuSeparator.hh =================================================================== --- src/FbTk/MenuSeparator.hh (Revision 3997) +++ src/FbTk/MenuSeparator.hh (Arbeitskopie) @@ -32,4 +32,5 @@ class MenuSeparator: public MenuItem { public: + bool iTypeCondition() const { return false; } virtual void draw(FbDrawable &drawable, const MenuTheme &theme, Index: src/FbTk/SearchResult.cc =================================================================== --- src/FbTk/SearchResult.cc (Revision 0) +++ src/FbTk/SearchResult.cc (Revision 0) @@ -0,0 +1,90 @@ +#include "SearchResult.hh" + +#include + +using namespace std; + +namespace FbTk{ + +std::vector < ITypeAheadable_Base* > SearchResult::unique(const SearchResult& other_search) const { + + BaseItems unique; + BaseItems::const_iterator it = m_results.begin(); + + for (; it != m_results.end(); it++){ + if (find(other_search.result().begin(), other_search.result().end(), *it) + == other_search.result().end()) { + unique.push_back(*it); + } + } + + return unique; +} + +ITypeAheadable_Base* SearchResult::prev() { + + // select last after first + if (m_selected == m_results[0] ){ + + m_selected = m_results[m_results.size()-1]; + return m_selected; + + } else { + + BaseItemsIt it = find(m_results.begin(), m_results.end(), m_selected); + m_selected = *(--it); + return m_selected; + } +} + +ITypeAheadable_Base* SearchResult::next(){ + + // select first after last + if (m_selected == m_results[m_results.size()-1]) { + + m_selected = m_results[0]; + return m_selected; + + } else { + + BaseItemsIt it = find(m_results.begin(), m_results.end(), m_selected); + m_selected = *(++it); + return m_selected; + } +} +bool SearchResult::seekAndSelect(){ + switch (m_results.size()){ + + case 0: + return false; + break; + + case 1: + m_seeked_string = m_results[0]->iTypeString(); + m_selected = m_results[0]; + return true; + break; + + default: + + bool seekforward = true; + for (int i=1; iiTypeCheckStringSize(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; + } + } + + if (m_selected == 0) { + m_selected = m_results[0]; + } + break; + + } +} +} Index: src/FbTk/Menu.hh =================================================================== --- src/FbTk/Menu.hh (Revision 3997) +++ src/FbTk/Menu.hh (Arbeitskopie) @@ -53,4 +53,15 @@ enum { RIGHT = 1, LEFT }; + struct _menu { + Pixmap frame_pixmap, title_pixmap, hilite_pixmap; + FbTk::FbWindow window, frame, title; + + std::string label; + int x_move, y_move, x_shift, y_shift, sublevels, persub, minsub, + grab_x, grab_y; + + unsigned int frame_h, item_w; + }; + /** Bullet type @@ -91,4 +102,5 @@ void enterSubmenu(); void enterParent(); + void pressReturn(XKeyEvent &event); void disableTitle(); @@ -185,5 +197,7 @@ bool exclusive_drawable = false); void clearItem(int index, bool clear = true); + void jumpToItem(int index); void highlightItem(int index); + virtual void redrawTitle(FbDrawable &pm); virtual void redrawFrame(FbDrawable &pm); @@ -188,9 +202,11 @@ virtual void redrawTitle(FbDrawable &pm); virtual void redrawFrame(FbDrawable &pm); - + std::vector const &Items() const { return menuitems; } virtual void internal_hide(); void update(FbTk::Subject *); + _menu menu; + private: @@ -219,14 +235,4 @@ Alignment m_alignment; - struct _menu { - Pixmap frame_pixmap, title_pixmap, hilite_pixmap; - FbTk::FbWindow window, frame, title; - - std::string label; - int x_move, y_move, x_shift, y_shift, sublevels, persub, minsub, - grab_x, grab_y; - - unsigned int frame_h, item_w; - } menu; int m_active_index; ///< current highlighted index Index: src/FbTk/MenuItem.cc =================================================================== --- src/FbTk/MenuItem.cc (Revision 3997) +++ src/FbTk/MenuItem.cc (Arbeitskopie) @@ -38,4 +38,77 @@ } +void MenuItem::drawLine(FbDrawable &draw, + const MenuTheme &theme, + int size, + int text_x, int text_y, + unsigned int width) const { + + unsigned int height = theme.itemHeight(); + int underline_offset = 2, line_x_begin, line_x_end; + + int height_offset = theme.itemHeight() - (theme.frameFont().height() + 2*theme.bevelWidth()); + text_y = text_y + theme.bevelWidth() + theme.frameFont().ascent() + height_offset/2; + + int text_w = theme.frameFont().textWidth( m_label.c_str(), m_label.size()); + + // string from beginning to end of searchstring + std::string search_string = m_label.substr(0,size); + + // width of the searchstring + int search_string_w = theme.frameFont().textWidth(search_string.c_str(), size); + + // next character after searchstring + std::string next_character = m_label.substr(size,1); + + // width of character + int next_character_w = theme.frameFont().textWidth(next_character.c_str(), 1); + + // pay attention to the text justification + switch(theme.frameFontJustify()) { + case FbTk::LEFT: + text_x = text_x + theme.bevelWidth() + height + 1; + break; + + case FbTk::RIGHT: + text_x = text_x + width - (height + theme.bevelWidth() + text_w); + break; + default: //center + text_x = text_x + ((width + 1 - text_w) / 2); + break; + } + + // use justify value from style + switch(theme.underlineJustify()) { + case FbTk::LEFT: + line_x_begin = text_x; + line_x_end = line_x_begin + search_string_w; + break; + case FbTk::RIGHT: + line_x_begin = text_x + search_string_w; + line_x_end = text_x + text_w; + break; + default: // center + line_x_begin = text_x + search_string_w; + line_x_end = line_x_begin + next_character_w; + break; + } + + // avoid drawing an ugly dot + if (size != 0 && search_string_w != text_w){ + draw.drawLine(theme.frameUnderlineGC().gc(), + line_x_begin, + text_y + underline_offset, + line_x_end, + text_y + underline_offset); + } else if ( search_string_w == text_w ){ + draw.drawLine(theme.frameUnderlineGC().gc(), + text_x, + text_y + underline_offset, + text_x + text_w, + text_y + underline_offset); + } + +} + void MenuItem::draw(FbDrawable &draw, const MenuTheme &theme, Index: src/FbTk/MenuTheme.hh =================================================================== --- src/FbTk/MenuTheme.hh (Revision 3997) +++ src/FbTk/MenuTheme.hh (Arbeitskopie) @@ -57,4 +57,5 @@ 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; } @@ -88,4 +89,5 @@ inline FbTk::Justify frameFontJustify() const { return *framefont_justify; } inline FbTk::Justify titleFontJustify() const { return *titlefont_justify; } + inline FbTk::Justify underlineJustify() const { return *underline_justify; } /** @@ -95,4 +97,5 @@ 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; } @@ -100,4 +103,5 @@ 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; } @@ -131,8 +135,8 @@ private: - FbTk::ThemeItem t_text, f_text, h_text, d_text; + FbTk::ThemeItem t_text, f_text, h_text, d_text, u_text; FbTk::ThemeItem title, frame, hilite; FbTk::ThemeItem titlefont, framefont; - FbTk::ThemeItem framefont_justify, titlefont_justify; + FbTk::ThemeItem framefont_justify, titlefont_justify, underline_justify; FbTk::ThemeItem bullet_pos; FbTk::ThemeItem m_bullet; @@ -145,5 +149,5 @@ 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; Index: src/FbTk/ITypeAheadable.hh =================================================================== --- src/FbTk/ITypeAheadable.hh (Revision 0) +++ src/FbTk/ITypeAheadable.hh (Revision 0) @@ -0,0 +1,37 @@ +#ifndef FBTK_ITYPEAHEADABLE_HH +#define FBTK_ITYPEAHEADABLE_HH + +#include + +namespace FbTk{ + +// abstract base class providing access and validation +class ITypeAheadable_Base{ +public: + virtual const std::string &iTypeString() const = 0; + char iTypeChar(int i) const { return iTypeString()[i]; } + bool iTypeCheckStringSize(int sz) const { return ( iTypeString().size() > sz ); } + bool iTypeCompareChar(char ch, int sz) const { + if (iTypeCheckStringSize(sz)){ + if ( iTypeChar(sz) == ch ) { + return true; + } else { + return false; + } + } else { + return false; + } + } +}; + +// specialized ITypeAheadable class, for use with any type of selection value. +template class ITypeAheadable : public ITypeAheadable_Base { +public: + virtual Value_Type iTypeValue() const = 0; + virtual bool iTypeCondition() const { + return ( iTypeString().size() > 0 ); + } +}; +} + +#endif // FBTK_ITYPEAHEADABLE_HH Index: src/FbTk/SearchResult.hh =================================================================== --- src/FbTk/SearchResult.hh (Revision 0) +++ src/FbTk/SearchResult.hh (Revision 0) @@ -0,0 +1,42 @@ +#ifndef FBTK_SEARCHRESULT_HH +#define FBTK_SEARCHRESULT_HH + +#include +#include "ITypeAheadable.hh" + +namespace FbTk { + +class SearchResult{ + +public: + + typedef std::vector < ITypeAheadable_Base* > BaseItems; + typedef BaseItems::iterator BaseItemsIt; + + SearchResult(const std::string &to_search_for) : + m_seeked_string(to_search_for), + m_selected(0) + {} + + void add(ITypeAheadable_Base* item){ m_results.push_back(item); } + BaseItems unique(const SearchResult& res) const; + int size() const { return m_results.size(); } + const BaseItems& result() const { return m_results; } + ITypeAheadable_Base* selected() const { return m_selected; } + void setSelected(ITypeAheadable_Base* item) { m_selected = item; } + const std::string& seekedString() const { return m_seeked_string; } + + ITypeAheadable_Base* prev(); + ITypeAheadable_Base* next(); + bool seekAndSelect(); + +private: + + BaseItems m_results; + ITypeAheadable_Base *m_selected; + std::string m_seeked_string; + +}; +} // end namespace FbTk + +#endif // FBTK_SEARCHRESULT_HH Index: src/FbTk/Makefile.am =================================================================== --- src/FbTk/Makefile.am (Revision 3997) +++ src/FbTk/Makefile.am (Arbeitskopie) @@ -51,4 +51,5 @@ MenuIcon.hh MenuIcon.cc \ stringstream.hh \ + TypeAhead.hh SearchResult.hh SearchResult.cc ITypeAheadable.hh \ Select2nd.hh \ ${xpm_SOURCE} \ Index: src/fluxbox.cc =================================================================== --- src/fluxbox.cc (Revision 3997) +++ src/fluxbox.cc (Arbeitskopie) @@ -218,4 +218,5 @@ "session.titlebar.right", "Session.Titlebar.Right"), m_rc_tabs_attach_area(m_resourcemanager, ATTACH_AREA_WINDOW, "session.tabsAttachArea", "Session.TabsAttachArea"), + m_rc_menu_type_ahead(m_resourcemanager, true, "session.useTypeahead", "Session.UseTypeahead"), m_rc_cache_life(m_resourcemanager, 5, "session.cacheLife", "Session.CacheLife"), m_rc_cache_max(m_resourcemanager, 200, "session.cacheMax", "Session.CacheMax"),