#include #include #include #include #include #include #include #include class DynmenuView; typedef DynmenuView *DynmenuViewRef; // boost::shared_ptr class Dynmenu { std::string m_title; std::string m_welcomeText; std::vector views; class Item { char *desc; Item(const Item &i) { assert(false); } public: std::string id; std::string key; std::string cmd; const char *getText() const { return desc; } void setText(const char *desc) { if(this->desc) delete [] this->desc; this->desc=new char[strlen(desc)+1]; strcpy(this->desc,desc); } Item(const char *id, const char *key, const char *desc, const char *cmd) : id(id), key(key), cmd(cmd) { this->desc=NULL; setText(desc); } ~Item() { delete [] desc; } }; std::vector items; typedef std::vector::iterator ItemIterator; struct has_id : public std::binary_function { bool operator ()( Item *item , const char *id) const { return item->id==id; } }; int m_selection; public: enum notify_id { TITLE, WELCOMETEXT, SELECTION }; void redraw() { sendNotify(TITLE); sendNotify(WELCOMETEXT); sendNotify(SELECTION); } void setTitle(const char *s) { m_title = s; sendNotify(TITLE); } const char*title() const { return m_title.c_str(); } void setWelcomeText(const char *s) { m_welcomeText = s; sendNotify(WELCOMETEXT); } const char *welcomeText() const { return m_welcomeText.c_str(); } void setItem(const char *id, const char *sortkey, const char *desc, const char *cmd); void delItem(const char *id); int selection() const { return m_selection; } bool haveSelection() const { return m_selection >= 0; } void selectItem(const char *id); const char *selectedCommand() const { return haveSelection() ? items[m_selection]->cmd.c_str() : ""; } void cursorUp() { if( m_selection>0) m_selection--; sendNotify(SELECTION); } void cursorDown() { if( m_selection+1items[index]->cmd.c_str() ); printf("itemInserted %p %i \"%s\"\n", menu, index, text ); } virtual void itemChanged(Dynmenu *menu, int index, const char *text ) { printf("itemChanged %p %i \"%s\"\n", menu, index, text ); } virtual ~DynmenuSimpleView() {} }; Dynmenu::ItemIterator Dynmenu::findItem(const char *id) { using namespace std; ItemIterator ret = find_if( items.begin(), items.end(), bind2nd( has_id(), id ) ); return ret; } void Dynmenu::setItem(const char *id, const char *sortkey, const char *desc, const char *cmd) { std::vector::iterator i; i = findItem(id); if(i==items.end()) { items.push_back( new Item(id,sortkey,desc,cmd) ); // TODO: sort sendItemInsertion( items.size()-1, items.back()->getText() ); if(m_selection==-1) { m_selection=items.size()-1; sendNotify(SELECTION); } } else { (*i)->cmd = cmd; (*i)->setText(desc); (*i)->key = sortkey; // TODO: resort sendItemChanged( i - items.begin(), (*i)->getText() ); } } void Dynmenu::selectItem(const char *id) { std::vector::iterator i; i = findItem(id); if(i!=items.end()) { m_selection = i - items.begin(); sendNotify(SELECTION); } } void Dynmenu::delItem(const char *id){} void Dynmenu::registerView(DynmenuViewRef view) { view->notify(this,TITLE); view->notify(this,WELCOMETEXT); std::vector::iterator i; for(i=items.begin(); i!=items.end(); i++ ) { view->itemInserted( this, i - items.begin(), (*i)->getText() ); } view->notify(this,SELECTION); views.push_back(view); } void Dynmenu::sendNotify(notify_id id) { for( std::vector::iterator i=views.begin(); i!=views.end(); i++ ) { (*i)->notify(this, id); } } void Dynmenu::sendItemInsertion(int idx, const char *text ) { for( std::vector::iterator i=views.begin(); i!=views.end(); i++ ) { (*i)->itemInserted(this, idx, text); } } void Dynmenu::sendItemChanged(int idx, const char *text ) { for( std::vector::iterator i=views.begin(); i!=views.end(); i++ ) { (*i)->itemChanged(this, idx, text); } } int _g_y = 0; #define kdprintf(fmt,...) \ { char buf[200]; _g_y++; sprintf( buf, fmt "\n", ## __VA_ARGS__); mvaddnstr(_g_y,1,buf,strlen(buf)); refresh(); } #define dprintf(fmt,...) struct TextBox { int y,x,w,h; TextBox() : y(0),x(0),w(0),h(0) {} void update(int x_, int y_, int w_, int h_) { y=y_;x=x_;w=w_;h=h_; } }; class LineDrawer { public: virtual void drawLine(WINDOW *win, int width, int number) = 0; virtual int size() const = 0; }; class ScrollWindow { WINDOW *win; int top_index; LineDrawer *drawer; void getExtants(int &maxy, int &maxx, int &count); public: void redraw(); ScrollWindow() : win(NULL), top_index(0), drawer(NULL) {} void setWin(WINDOW *w) { win=w; redraw(); } void setLineDrawer(LineDrawer *d) { drawer = d; redraw(); } void doScroll(int delta); void scrollTo(int start, int end); void invalidate(int start, int end); void changedSize(); }; void ScrollWindow::getExtants(int &maxy, int &maxx, int &count) { getmaxyx(win,maxy,maxx); count = drawer->size(); } void ScrollWindow::redraw() { if(!win || !drawer) return; int maxy,maxx,count; getExtants(maxy,maxx,count); wborder(win, 0, 0, top_index>0 ? '.' : 0, count < top_index+maxy-1 ? 0 : '.' , 0, 0, 0, 0); for(int y=1,i=top_index;ydrawLine(win,maxx-2,i++); } } wrefresh(win); } void ScrollWindow::invalidate(int start, int end) { int maxy,maxx,count; if( end <= top_index ) return; getExtants(maxy,maxx,count); if( start > top_index+maxy-2 ) return; if(start < top_index ) start = top_index; if(end > top_index+maxy-2 ) end = top_index+maxy-2; //if(end > count ) end = count; int starty = 1 + start - top_index; int stopy = starty + end - start; for(int y=starty,i=start;ydrawLine(win,maxx-2,i++); //} } wrefresh(win); } void ScrollWindow::scrollTo(int start, int end) { if( start < top_index ) doScroll(start-top_index); int maxy,maxx,count; getExtants(maxy,maxx,count); if( end > top_index+maxy-2 && start>top_index) { int delta=end-top_index-maxy+2; if( top_index+delta > start ) delta = start - top_index; doScroll(delta); } } void ScrollWindow::changedSize() { int maxy,maxx,count; getExtants(maxy,maxx,count); wmove(win,maxy-1,1); if( top_index+maxy-2=-1 && delta<=1); int maxy,maxx,count; getExtants(maxy,maxx,count); if( top_index+delta<0 ) delta = -top_index; if (delta>0 && top_index>0 && top_index+maxy-1+delta>count) delta=count-top_index-maxy+2; if (top_index+delta>count) delta=0; if(delta==0) return; dprintf("scrolling win=%p, drawer=%p, top_index+delta=%i, v=%i, count=%i", win, drawer, top_index+delta, top_index+maxy-1+delta, count); dprintf("scrolling %i", delta); WINDOW *w = derwin(win, maxy-2,maxx-2,1,1); scrollok(w,true); wscrl(w,delta); scrollok(w,false); touchwin(win); if( top_index==0 ) { wmove(win,0,1); whline(win,'.',maxx-2); } if( top_index+maxy-2==count) { wmove(win,maxy-1,1); whline(win,'.',maxx-2); } top_index += delta; int y = delta<0 ? 0 : maxy-2-delta; int i = y + top_index; dprintf("scroll values i=%i, y=%i", i, y); delta = delta<0 ? -delta : delta; for(int j=0;jdrawLine(w,maxx-2,i++); } wrefresh(w); delwin(w); if( top_index==0 ) { wmove(win,0,1); whline(win,0,maxx-2); } if( top_index+maxy-2==count) { wmove(win,maxy-1,1); whline(win,0,maxx-2); } wrefresh(win); } /* class ItemDrawer : public LineDrawer { public: void drawLine(WINDOW *win, int width, int number); int size() const { return 50; } }; void ItemDrawer::drawLine(WINDOW *win, int width, int number) { char buf[200]; sprintf(buf, "This is line #%i.", number ); waddnstr(win,buf, width); } */ class DynmenuCursesView : public DynmenuView, public LineDrawer { int maxx, maxy; WINDOW *win; TextBox titleBox; TextBox welcomeBox; WINDOW *itemwin; ScrollWindow itemScroll; struct Item { int top; std::vector text; int compare(int line) { if(line=top+text.size()) return 1; else return 0; } }; std::vector items; int cached_index; int selected; public: DynmenuCursesView() : win(NULL),itemwin(NULL), maxx(0), maxy(0), cached_index(0), selected(-1) { itemScroll.setLineDrawer(this); } ~DynmenuCursesView() { delwin(itemwin); } void show(WINDOW *win); void notify(Dynmenu *menu, Dynmenu::notify_id id); void itemInserted(Dynmenu *menu, int index, const char *text ); void itemChanged(Dynmenu *menu, int index, const char *text ); // LineDrawer interface void drawLine(WINDOW *win, int width, int number); int size() const { if(items.size()==0) return 0; else return items.back().top + items.back().text.size(); } }; void DynmenuCursesView::drawLine(WINDOW *win, int width, int number ) { int i = cached_index; while( items[i].compare(number)>0 && i0 ) i--; if( items[i].compare(number)!=0 ) { // If out of range, clear row wattroff(win, A_STANDOUT); for(int i=0;iwidth ? width : len; // TODO: draw selection if( selected == cached_index ) wattron(win, A_STANDOUT); else wattroff(win, A_STANDOUT); waddnstr( win, item.text[i], len ); int mright = width - len; for(int j=0;j::iterator item = items.insert( items.begin() + index, Item() ); const char *p = text; item->text.push_back(p); for(;;) { while( *p && *p!='\n' ) p++; if(*p) p++; if(*p) item->text.push_back(p); else break; } int firstrow = 0; if(item!=items.begin()) { item->top = (item-1)->top + (item-1)->text.size(); } else item->top = 0; firstrow = item->top; if((item+1)!=items.end()) { size_t newrows = item->text.size(); for( item++; item!=items.end(); item++ ) item->top += newrows; } itemScroll.invalidate( firstrow, size() ); itemScroll.changedSize(); wrefresh(win); } void DynmenuCursesView::itemChanged(Dynmenu *menu, int index, const char *text) { std::vector::iterator item = items.begin() + index; size_t oldsize = item->text.size(); int lastrow = items.back().top + items.back().text.size(); item->text.clear(); const char *p = text; item->text.push_back(p); for(;;) { while( *p && *p!='\n' ) p++; if(*p) p++; if(*p) item->text.push_back(p); else break; } int firstrow = item->top; int diff = item->text.size() - oldsize; if( diff ) { for( item ++; item!=items.end(); item++ ) item->top += diff; item = items.end() - 1; } else lastrow = item->top + oldsize; if( lastrow < item->top + item->text.size() ) lastrow = item->top + item->text.size(); itemScroll.invalidate( firstrow, lastrow ); itemScroll.changedSize(); wrefresh(win); } void getTextExtents(const char *text, int &width, int &height ) { width = 0; height = 0; const char *p; const char *s; p = text; do { s = p; while( *p && *p!='\n' ) p++; if(p-s>0) { height++; if(widthtitle(), tw, th ); tx = 0; ty = 0; tw = maxx - tx; dprintf("tx = %i, ty=%i, title=%s, tw=%i", tx, ty, menu->title(), tw ); clearBox(win,titleBox); oldbottom = titleBox.y+titleBox.h; titleBox.update(tx,ty,tw,th); if(oldbottom!=titleBox.y+titleBox.h) { notify(menu,Dynmenu::WELCOMETEXT); } drawText(win,titleBox, menu->title(), true ); break; case Dynmenu::WELCOMETEXT: getTextExtents( menu->welcomeText(), tw, th ); tx = ( maxx - tw )/ 2; ty = titleBox.y+titleBox.h+2; clearBox(win,welcomeBox); if(ty+th!=welcomeBox.y+welcomeBox.h) { // Need to resize itemsBox if(itemwin) { werase(itemwin); delwin(itemwin); } itemwin = derwin(win,maxy-ty-th-1,maxx-3, ty+th, 2); itemScroll.setWin(itemwin); } welcomeBox.update(tx,ty,tw,th); drawText(win,welcomeBox, menu->welcomeText() ); //mvwaddstr(win,ty,tx, menu->welcomeText() ); break; case Dynmenu::SELECTION: //dprintf("items=%zu, selection=%i", menu->items.size(), menu->selection() ); int sel=selected; selected = menu->selection(); if(sel!=-1) itemScroll.invalidate(items[sel].top, items[sel].top+items[sel].text.size() ); sel = selected; if(sel!=-1) { itemScroll.invalidate(items[sel].top, items[sel].top+items[sel].text.size() ); itemScroll.scrollTo(items[sel].top, items[sel].top+items[sel].text.size() ); wrefresh(itemwin); } break; } wrefresh(win); } void DynmenuCursesView::show(WINDOW *win) { if( win ) { this->win = win; getmaxyx(win,maxy,maxx); //box(win, 0, 0); if(itemwin) { wrefresh(itemwin); } wrefresh(win); } else { } } #include #include using std::string; using std::isspace; class Parser { int peeked; void *gdata; int (*getter)(void *); int peek() { return peeked==-2 ? (peeked=getter(gdata)) : peeked; } int get() { int retv = peeked==-2? (peeked=getter(gdata)) : peeked; peeked=-2; return retv;} bool peekedSpace() { return isspace(peek()); } void skipSpace() { while(peekedSpace()) get(); } public: enum tokentype { Identifier, String, EOL }; enum exception { NoGetter, SyntaxError }; void setCharGetter( int (*g)(void*), void *user ) { getter=g; gdata=user; } void expect( tokentype t, std::string &s ); Parser() : peeked(-2), getter(NULL), gdata(NULL) {} }; void Parser::expect( tokentype t, std::string &s ) { if(! getter ) throw NoGetter; int c; switch( t ) { case Identifier: skipSpace(); s = ""; while( peek()!=-1 && ! peekedSpace() ) s += (char)get() ; break; case String: skipSpace(); s = ""; if( peek() !='"' ) throw SyntaxError; get(); // peel off open-quote while(peek()!='"' && peek()!=-1 ) { c = get(); if( c=='\\') { c=get(); // allow escaped quotes if(c=='n') c='\n'; else if(c=='t') c='\t'; } s += (char)c ; } get(); // peel off close-quote break; case EOL: while( peek()!=-1 && isspace(peek()) && peek()!='\n' ) get(); if( !isspace(peek()) ) throw SyntaxError; break; } } #include #include #include #include #define CTRLL 12 int buf_getc( const char **p ) { if(**p) return *((*p)++); else return -1; } void trap_signals() { struct sigaction s; s.sa_handler = SIG_IGN; int sig[] = { SIGINT, SIGTSTP, SIGQUIT, }; for (int i=0; i= 0) { if (FD_ISSET(0, &fds)) { //dprintf("char !");//space!"); int c = getch(); if (c == ERR) { perror("error reading stdin"); return 1; } switch (c) { case KEY_UP: case 'k': menu.cursorUp(); break; case KEY_DOWN: case 'j': menu.cursorDown(); break; case KEY_RIGHT: case '\n': case '\x0d': case 'l': case KEY_ENTER: if (!menu.haveSelection()) break; bye(); endwin(); execl("/bin/sh", "/bin/sh", "-c", menu.selectedCommand(), (char *) NULL); perror("exec failed"); exit(1); break; case 'Q': bye(); endwin(); exit(0); break; #ifndef SIMPLE case 'r': case CTRLL: menu.redraw(); break; #endif /* case ' ': { dprintf("space!"); static char b = 'A'; char buf[80]; sprintf( buf, "Letter '%c'", b++ ); menu.setItem( "id3", "3", buf, "id3 cmd"); } */ break; } } if (FD_ISSET(fd, &fds)) { Parser *p = new Parser; char line[1024];//[PIPE_BUF]; assert(line); char *pointer = line; while (fgets(line, 1024, fifo) != NULL) { dprintf("%s", line ); try { pointer = line; string menuMethod(1024,' '); string id(1024, ' '); string sortkey(1024, ' '); string desc(1024, ' '); string cmd(1024, ' '); string dummy; p->setCharGetter( (int (*)(void*))buf_getc, (void*)&pointer ); p->expect( Parser::Identifier, menuMethod ); if( menuMethod=="setItem" ) { p->expect( Parser::String, id ); p->expect( Parser::String, sortkey ); p->expect( Parser::String, desc ); p->expect( Parser::String, cmd ); menu.setItem( id.c_str(), sortkey.c_str(), desc.c_str(), cmd.c_str() ); } else if( menuMethod=="setTitle" ) { p->expect( Parser::String, desc ); menu.setTitle( desc.c_str() ); } else if( menuMethod=="setWelcomeText" ) { p->expect( Parser::String, desc ); menu.setWelcomeText( desc.c_str() ); } else if( menuMethod=="delItem" ) { p->expect( Parser::String, id ); menu.delItem( id.c_str() ); } else if( menuMethod=="selectItem" ) { p->expect( Parser::String, id ); menu.selectItem( id.c_str() ); } } catch(...) { dprintf("exception"); } } } FD_ZERO(&fds); if (!feof(fifo)) // will never eof if it is a fifo FD_SET(fd, &fds); // could use inotify here... FD_SET(0, &fds); } char ebuf[80] =""; switch(errno) { case EINTR: menu.redraw(); goto top; case EBADF: strcpy(ebuf, "EBADF"); break; case EINVAL: strcpy(ebuf, "EINVAL"); break; case ENOMEM: strcpy(ebuf, "ENOMEM"); break; default: sprintf(ebuf, "0x%X", errno); } endwin(); printf(" errno = %s\n", ebuf ); return 0; } // vim:ts=2 sw=2 et: