added support for typeahead in menus

This commit is contained in:
markt 2007-03-03 19:35:34 +00:00
parent d6a7bd786f
commit a233229bd8
12 changed files with 538 additions and 79 deletions

View file

@ -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:

View file

@ -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

View file

@ -51,6 +51,7 @@ libFbTk_a_SOURCES = App.hh App.cc Color.cc Color.hh Command.hh \
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} \

View file

@ -110,6 +110,7 @@ Menu::Menu(MenuTheme &tm, ImageControl &imgctrl):
m_visible = false;
m_type_ahead.init(menuitems);
menu.x_move =
menu.y_move = 0;
@ -205,15 +206,24 @@ int Menu::insert(const FbString &label, Menu *submenu, int 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 @@ int Menu::remove(unsigned int index) {
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 @@ int Menu::remove(unsigned int index) {
}
void Menu::removeAll() {
while (!menuitems.empty()) {
remove(0);
}
m_need_update = true;
while (!menuitems.empty())
remove(menuitems.size()-1);
}
void Menu::raise() {
@ -271,18 +282,46 @@ 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;
// 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;
// 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;
}
// 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 (new_index == -1)
return;
// 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 = 0;
m_active_index = new_index;
if (validIndex(old_active_index) &&
menuitems[old_active_index] != 0) {
if (menuitems[old_active_index]->submenu()) {
@ -292,54 +331,7 @@ void Menu::nextItem(int failsafe) {
}
clearItem(old_active_index);
}
if (menuitems[m_active_index] == 0) {
m_active_index = -1;
return;
}
if (!isItemSelectable(m_active_index) && m_active_index != failsafe) {
nextItem(failsafe);
return;
}
clearItem(m_active_index);
}
void Menu::prevItem(int failsafe) {
if (menuitems.empty())
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 = menuitems.size() - 1;
if (validIndex(old_active_index)) {
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();
}
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 @@ void Menu::enterSubmenu() {
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 @@ void Menu::keyPressEvent(XKeyEvent &event) {
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 @@ void Menu::renderForeground(FbWindow &win, FbDrawable &drawable) {
// 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;
@ -1160,9 +1177,16 @@ void Menu::clearItem(int index, bool clear) {
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 @@ void Menu::clearItem(int index, bool clear) {
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
@ -1206,4 +1233,36 @@ void Menu::highlightItem(int index) {
}
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

View file

@ -41,6 +41,7 @@
#include "MenuTheme.hh"
#include "Timer.hh"
#include "FbString.hh"
#include "TypeAhead.hh"
namespace FbTk {
@ -87,10 +88,8 @@ public:
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 @@ protected:
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);
@ -209,6 +208,14 @@ private:
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;
bool m_moving; ///< if we're moving/draging or not

View file

@ -38,6 +38,44 @@ void MenuItem::click(int button, int time) {
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,

View file

@ -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 @@ public:
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 @@ private:
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;

View file

@ -44,6 +44,7 @@ MenuTheme::MenuTheme(int screen_num):
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 @@ MenuTheme::MenuTheme(int screen_num):
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 @@ MenuTheme::MenuTheme(int screen_num):
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 @@ void MenuTheme::reconfigTheme() {
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());

View file

@ -56,6 +56,7 @@ public:
///@{
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 @@ public:
///@{
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 @@ public:
}
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 @@ private:
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;

51
src/FbTk/SearchResult.cc Normal file
View file

@ -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

53
src/FbTk/SearchResult.hh Normal file
View file

@ -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

174
src/FbTk/TypeAhead.hh Normal file
View file

@ -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