diff options
Diffstat (limited to 'src/dynmenu.cpp')
-rw-r--r-- | src/dynmenu.cpp | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/src/dynmenu.cpp b/src/dynmenu.cpp new file mode 100644 index 0000000..d231639 --- /dev/null +++ b/src/dynmenu.cpp | |||
@@ -0,0 +1,821 @@ | |||
1 | #include <unistd.h> | ||
2 | #include <curses.h> | ||
3 | #include <string.h> | ||
4 | #include <string> | ||
5 | #include <vector> | ||
6 | #include <assert.h> | ||
7 | #include <algorithm> | ||
8 | #include <signal.h> | ||
9 | |||
10 | class DynmenuView; | ||
11 | typedef DynmenuView *DynmenuViewRef; // boost::shared_ptr<DynmenuView> | ||
12 | |||
13 | class Dynmenu | ||
14 | { | ||
15 | std::string m_title; | ||
16 | std::string m_welcomeText; | ||
17 | std::vector<DynmenuViewRef> views; | ||
18 | |||
19 | class Item { | ||
20 | char *desc; | ||
21 | Item(const Item &i) { assert(false); } | ||
22 | public: | ||
23 | std::string id; | ||
24 | std::string key; | ||
25 | std::string cmd; | ||
26 | const char *getText() const { return desc; } | ||
27 | void setText(const char *desc) { | ||
28 | if(this->desc) delete [] this->desc; | ||
29 | this->desc=new char[strlen(desc)+1]; | ||
30 | strcpy(this->desc,desc); | ||
31 | } | ||
32 | Item(const char *id, const char *key, const char *desc, const char *cmd) : | ||
33 | id(id), key(key), cmd(cmd) { this->desc=NULL; setText(desc); } | ||
34 | ~Item() { delete [] desc; } | ||
35 | }; | ||
36 | |||
37 | std::vector<Item *> items; | ||
38 | |||
39 | typedef std::vector<Item *>::iterator ItemIterator; | ||
40 | struct has_id : public std::binary_function<Item*,const char *,bool> { | ||
41 | bool operator ()( Item *item , const char *id) const { return item->id==id; } | ||
42 | }; | ||
43 | |||
44 | int m_selection; | ||
45 | |||
46 | public: | ||
47 | |||
48 | enum notify_id { TITLE, WELCOMETEXT, SELECTION }; | ||
49 | void redraw() { sendNotify(TITLE); sendNotify(WELCOMETEXT); sendNotify(SELECTION); } | ||
50 | void setTitle(const char *s) { m_title = s; sendNotify(TITLE); } | ||
51 | const char*title() const { return m_title.c_str(); } | ||
52 | void setWelcomeText(const char *s) { m_welcomeText = s; sendNotify(WELCOMETEXT); } | ||
53 | const char *welcomeText() const { return m_welcomeText.c_str(); } | ||
54 | void setItem(const char *id, const char *sortkey, const char *desc, const char *cmd); | ||
55 | void delItem(const char *id); | ||
56 | int selection() const { return m_selection; } | ||
57 | bool haveSelection() const { return m_selection >= 0; } | ||
58 | void selectItem(const char *id); | ||
59 | const char *selectedCommand() const { return haveSelection() ? items[m_selection]->cmd.c_str() : ""; } | ||
60 | void cursorUp() { if( m_selection>0) m_selection--; sendNotify(SELECTION); } | ||
61 | void cursorDown() { if( m_selection+1<items.size()) m_selection++; sendNotify(SELECTION); } | ||
62 | Dynmenu() { m_selection = -1; } | ||
63 | ~Dynmenu() { for(int i=0;i<items.size();i++) delete items[i]; } | ||
64 | |||
65 | void registerView(DynmenuViewRef view); | ||
66 | private: | ||
67 | void sendNotify(notify_id id); | ||
68 | void sendItemInsertion(int idx, const char *text); | ||
69 | void sendItemChanged(int idx, const char *text); | ||
70 | ItemIterator findItem(const char *id); | ||
71 | }; | ||
72 | |||
73 | class DynmenuView | ||
74 | { | ||
75 | public: | ||
76 | virtual void notify(Dynmenu *menu, Dynmenu::notify_id id) = 0; | ||
77 | virtual void itemInserted(Dynmenu *menu, int index, const char *text ) = 0; | ||
78 | virtual void itemChanged(Dynmenu *menu, int index, const char *text ) = 0; | ||
79 | virtual ~DynmenuView() {} | ||
80 | }; | ||
81 | |||
82 | class DynmenuSimpleView : public DynmenuView | ||
83 | { | ||
84 | public: | ||
85 | virtual void notify(Dynmenu *menu, Dynmenu::notify_id id) { | ||
86 | printf("notify %p %i\n", menu, id ); | ||
87 | } | ||
88 | virtual void itemInserted(Dynmenu *menu, int index, const char *text ) { | ||
89 | //printf("itemInserted %p %i \"%s\" \"%s\"\n", menu, index, text, menu->items[index]->cmd.c_str() ); | ||
90 | printf("itemInserted %p %i \"%s\"\n", menu, index, text ); | ||
91 | } | ||
92 | virtual void itemChanged(Dynmenu *menu, int index, const char *text ) { | ||
93 | printf("itemChanged %p %i \"%s\"\n", menu, index, text ); | ||
94 | } | ||
95 | virtual ~DynmenuSimpleView() {} | ||
96 | }; | ||
97 | |||
98 | |||
99 | Dynmenu::ItemIterator Dynmenu::findItem(const char *id) | ||
100 | { | ||
101 | using namespace std; | ||
102 | ItemIterator ret = find_if( items.begin(), items.end(), bind2nd( has_id(), id ) ); | ||
103 | return ret; | ||
104 | } | ||
105 | |||
106 | void Dynmenu::setItem(const char *id, const char *sortkey, const char *desc, const char *cmd) | ||
107 | { | ||
108 | std::vector<Item*>::iterator i; | ||
109 | i = findItem(id); | ||
110 | if(i==items.end()) { | ||
111 | items.push_back( new Item(id,sortkey,desc,cmd) ); | ||
112 | // TODO: sort | ||
113 | sendItemInsertion( items.size()-1, items.back()->getText() ); | ||
114 | if(m_selection==-1) { | ||
115 | m_selection=items.size()-1; | ||
116 | sendNotify(SELECTION); | ||
117 | } | ||
118 | } | ||
119 | else { | ||
120 | (*i)->cmd = cmd; | ||
121 | (*i)->setText(desc); | ||
122 | (*i)->key = sortkey; // TODO: resort | ||
123 | sendItemChanged( i - items.begin(), (*i)->getText() ); | ||
124 | } | ||
125 | } | ||
126 | |||
127 | void Dynmenu::selectItem(const char *id) | ||
128 | { | ||
129 | std::vector<Item*>::iterator i; | ||
130 | i = findItem(id); | ||
131 | if(i!=items.end()) { | ||
132 | m_selection = i - items.begin(); | ||
133 | sendNotify(SELECTION); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | void Dynmenu::delItem(const char *id){} | ||
138 | |||
139 | void Dynmenu::registerView(DynmenuViewRef view) | ||
140 | { | ||
141 | view->notify(this,TITLE); | ||
142 | view->notify(this,WELCOMETEXT); | ||
143 | std::vector<Item*>::iterator i; | ||
144 | for(i=items.begin(); i!=items.end(); i++ ) { | ||
145 | view->itemInserted( this, i - items.begin(), (*i)->getText() ); | ||
146 | } | ||
147 | view->notify(this,SELECTION); | ||
148 | views.push_back(view); | ||
149 | } | ||
150 | |||
151 | void Dynmenu::sendNotify(notify_id id) | ||
152 | { | ||
153 | for( std::vector<DynmenuViewRef>::iterator i=views.begin(); i!=views.end(); i++ ) { | ||
154 | (*i)->notify(this, id); | ||
155 | } | ||
156 | } | ||
157 | void Dynmenu::sendItemInsertion(int idx, const char *text ) | ||
158 | { | ||
159 | for( std::vector<DynmenuViewRef>::iterator i=views.begin(); i!=views.end(); i++ ) { | ||
160 | (*i)->itemInserted(this, idx, text); | ||
161 | } | ||
162 | } | ||
163 | void Dynmenu::sendItemChanged(int idx, const char *text ) | ||
164 | { | ||
165 | for( std::vector<DynmenuViewRef>::iterator i=views.begin(); i!=views.end(); i++ ) { | ||
166 | (*i)->itemChanged(this, idx, text); | ||
167 | } | ||
168 | } | ||
169 | |||
170 | int _g_y = 0; | ||
171 | |||
172 | #define kdprintf(fmt,...) \ | ||
173 | { char buf[200]; _g_y++; sprintf( buf, fmt "\n", ## __VA_ARGS__); mvaddnstr(_g_y,1,buf,strlen(buf)); refresh(); } | ||
174 | #define dprintf(fmt,...) | ||
175 | |||
176 | |||
177 | struct TextBox | ||
178 | { | ||
179 | int y,x,w,h; | ||
180 | TextBox() : y(0),x(0),w(0),h(0) {} | ||
181 | void update(int x_, int y_, int w_, int h_) { y=y_;x=x_;w=w_;h=h_; } | ||
182 | }; | ||
183 | |||
184 | |||
185 | class LineDrawer | ||
186 | { | ||
187 | public: | ||
188 | virtual void drawLine(WINDOW *win, int width, int number) = 0; | ||
189 | virtual int size() const = 0; | ||
190 | }; | ||
191 | |||
192 | class ScrollWindow | ||
193 | { | ||
194 | WINDOW *win; | ||
195 | int top_index; | ||
196 | LineDrawer *drawer; | ||
197 | void getExtants(int &maxy, int &maxx, int &count); | ||
198 | public: | ||
199 | void redraw(); | ||
200 | ScrollWindow() : win(NULL), top_index(0), drawer(NULL) {} | ||
201 | void setWin(WINDOW *w) { win=w; redraw(); } | ||
202 | void setLineDrawer(LineDrawer *d) { drawer = d; redraw(); } | ||
203 | void doScroll(int delta); | ||
204 | void scrollTo(int start, int end); | ||
205 | void invalidate(int start, int end); | ||
206 | void changedSize(); | ||
207 | }; | ||
208 | |||
209 | void ScrollWindow::getExtants(int &maxy, int &maxx, int &count) | ||
210 | { | ||
211 | getmaxyx(win,maxy,maxx); | ||
212 | count = drawer->size(); | ||
213 | } | ||
214 | |||
215 | void ScrollWindow::redraw() | ||
216 | { | ||
217 | if(!win || !drawer) return; | ||
218 | int maxy,maxx,count; | ||
219 | getExtants(maxy,maxx,count); | ||
220 | wborder(win, 0, 0, top_index>0 ? '.' : 0, count < top_index+maxy-1 ? 0 : '.' , 0, 0, 0, 0); | ||
221 | for(int y=1,i=top_index;y<maxy-1;y++) { | ||
222 | if( i<count ) { | ||
223 | wmove(win,y,1); | ||
224 | drawer->drawLine(win,maxx-2,i++); | ||
225 | } | ||
226 | } | ||
227 | wrefresh(win); | ||
228 | } | ||
229 | |||
230 | void ScrollWindow::invalidate(int start, int end) | ||
231 | { | ||
232 | int maxy,maxx,count; | ||
233 | if( end <= top_index ) return; | ||
234 | getExtants(maxy,maxx,count); | ||
235 | if( start > top_index+maxy-2 ) return; | ||
236 | |||
237 | if(start < top_index ) start = top_index; | ||
238 | if(end > top_index+maxy-2 ) end = top_index+maxy-2; | ||
239 | //if(end > count ) end = count; | ||
240 | |||
241 | |||
242 | int starty = 1 + start - top_index; | ||
243 | int stopy = starty + end - start; | ||
244 | |||
245 | for(int y=starty,i=start;y<stopy;y++) { | ||
246 | //if( i<count ) { | ||
247 | wmove(win,y,1); | ||
248 | drawer->drawLine(win,maxx-2,i++); | ||
249 | //} | ||
250 | } | ||
251 | wrefresh(win); | ||
252 | } | ||
253 | |||
254 | void ScrollWindow::scrollTo(int start, int end) | ||
255 | { | ||
256 | if( start < top_index ) doScroll(start-top_index); | ||
257 | int maxy,maxx,count; | ||
258 | getExtants(maxy,maxx,count); | ||
259 | if( end > top_index+maxy-2 && start>top_index) { | ||
260 | int delta=end-top_index-maxy+2; | ||
261 | if( top_index+delta > start ) delta = start - top_index; | ||
262 | doScroll(delta); | ||
263 | } | ||
264 | } | ||
265 | |||
266 | void ScrollWindow::changedSize() | ||
267 | { | ||
268 | int maxy,maxx,count; | ||
269 | getExtants(maxy,maxx,count); | ||
270 | wmove(win,maxy-1,1); | ||
271 | if( top_index+maxy-2<count) { | ||
272 | whline(win,'.',maxx-2); | ||
273 | wrefresh(win); | ||
274 | } | ||
275 | else if( top_index+maxy-2==count) { | ||
276 | whline(win,0,maxx-2); | ||
277 | wrefresh(win); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | void ScrollWindow::doScroll(int delta) | ||
282 | { | ||
283 | if(!win || !drawer) return; | ||
284 | //assert(delta>=-1 && delta<=1); | ||
285 | int maxy,maxx,count; | ||
286 | getExtants(maxy,maxx,count); | ||
287 | |||
288 | if( top_index+delta<0 ) delta = -top_index; | ||
289 | if (delta>0 && top_index>0 && top_index+maxy-1+delta>count) delta=count-top_index-maxy+2; | ||
290 | if (top_index+delta>count) delta=0; | ||
291 | |||
292 | if(delta==0) return; | ||
293 | 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); | ||
294 | |||
295 | dprintf("scrolling %i", delta); | ||
296 | |||
297 | WINDOW *w = derwin(win, maxy-2,maxx-2,1,1); | ||
298 | scrollok(w,true); | ||
299 | wscrl(w,delta); | ||
300 | scrollok(w,false); | ||
301 | touchwin(win); | ||
302 | |||
303 | if( top_index==0 ) { | ||
304 | wmove(win,0,1); | ||
305 | whline(win,'.',maxx-2); | ||
306 | } | ||
307 | if( top_index+maxy-2==count) { | ||
308 | wmove(win,maxy-1,1); | ||
309 | whline(win,'.',maxx-2); | ||
310 | } | ||
311 | top_index += delta; | ||
312 | int y = delta<0 ? 0 : maxy-2-delta; | ||
313 | int i = y + top_index; | ||
314 | dprintf("scroll values i=%i, y=%i", i, y); | ||
315 | delta = delta<0 ? -delta : delta; | ||
316 | for(int j=0;j<delta;j++) { | ||
317 | wmove(w,y++,0); | ||
318 | if(i<count) drawer->drawLine(w,maxx-2,i++); | ||
319 | |||
320 | } | ||
321 | wrefresh(w); | ||
322 | delwin(w); | ||
323 | if( top_index==0 ) { | ||
324 | wmove(win,0,1); | ||
325 | whline(win,0,maxx-2); | ||
326 | } | ||
327 | if( top_index+maxy-2==count) { | ||
328 | wmove(win,maxy-1,1); | ||
329 | whline(win,0,maxx-2); | ||
330 | } | ||
331 | wrefresh(win); | ||
332 | } | ||
333 | |||
334 | /* | ||
335 | class ItemDrawer : public LineDrawer | ||
336 | { | ||
337 | public: | ||
338 | void drawLine(WINDOW *win, int width, int number); | ||
339 | int size() const { return 50; } | ||
340 | }; | ||
341 | |||
342 | void ItemDrawer::drawLine(WINDOW *win, int width, int number) | ||
343 | { | ||
344 | char buf[200]; | ||
345 | sprintf(buf, "This is line #%i.", number ); | ||
346 | waddnstr(win,buf, width); | ||
347 | } | ||
348 | */ | ||
349 | |||
350 | class DynmenuCursesView : public DynmenuView, public LineDrawer | ||
351 | { | ||
352 | int maxx, maxy; | ||
353 | WINDOW *win; | ||
354 | TextBox titleBox; | ||
355 | TextBox welcomeBox; | ||
356 | WINDOW *itemwin; | ||
357 | ScrollWindow itemScroll; | ||
358 | struct Item { | ||
359 | int top; | ||
360 | std::vector<const char *> text; | ||
361 | int compare(int line) { | ||
362 | if(line<top) return -1; | ||
363 | else if(line>=top+text.size()) return 1; | ||
364 | else return 0; | ||
365 | } | ||
366 | }; | ||
367 | std::vector<Item> items; | ||
368 | int cached_index; | ||
369 | int selected; | ||
370 | public: | ||
371 | DynmenuCursesView() : win(NULL),itemwin(NULL), maxx(0), maxy(0), cached_index(0), selected(-1) { | ||
372 | itemScroll.setLineDrawer(this); | ||
373 | } | ||
374 | ~DynmenuCursesView() { delwin(itemwin); } | ||
375 | void show(WINDOW *win); | ||
376 | void notify(Dynmenu *menu, Dynmenu::notify_id id); | ||
377 | void itemInserted(Dynmenu *menu, int index, const char *text ); | ||
378 | void itemChanged(Dynmenu *menu, int index, const char *text ); | ||
379 | |||
380 | // LineDrawer interface | ||
381 | void drawLine(WINDOW *win, int width, int number); | ||
382 | int size() const { if(items.size()==0) return 0; else return items.back().top + items.back().text.size(); } | ||
383 | }; | ||
384 | |||
385 | void DynmenuCursesView::drawLine(WINDOW *win, int width, int number ) | ||
386 | { | ||
387 | int i = cached_index; | ||
388 | while( items[i].compare(number)>0 && i<items.size()-1 ) i++; | ||
389 | while( items[i].compare(number)<0 && i>0 ) i--; | ||
390 | if( items[i].compare(number)!=0 ) { | ||
391 | // If out of range, clear row | ||
392 | wattroff(win, A_STANDOUT); | ||
393 | for(int i=0;i<width;i++) waddch(win,' '); | ||
394 | return; | ||
395 | } | ||
396 | cached_index = i; | ||
397 | Item &item = items[cached_index]; | ||
398 | i = number - item.top; | ||
399 | const char *end = NULL; | ||
400 | if( i < item.text.size()-1 ) end = item.text[i+1]-1; | ||
401 | if(!end) for(end=item.text[i];*end;end++); | ||
402 | size_t len = end - item.text[i]; | ||
403 | len = len>width ? width : len; | ||
404 | // TODO: draw selection | ||
405 | if( selected == cached_index ) | ||
406 | wattron(win, A_STANDOUT); | ||
407 | else | ||
408 | wattroff(win, A_STANDOUT); | ||
409 | waddnstr( win, item.text[i], len ); | ||
410 | int mright = width - len; | ||
411 | for(int j=0;j<mright;j++) waddstr(win," "); | ||
412 | wattroff(win, A_STANDOUT); | ||
413 | } | ||
414 | |||
415 | void DynmenuCursesView::itemInserted(Dynmenu *menu, int index, const char *text) | ||
416 | { | ||
417 | std::vector<Item>::iterator item = items.insert( items.begin() + index, Item() ); | ||
418 | const char *p = text; | ||
419 | item->text.push_back(p); | ||
420 | for(;;) { | ||
421 | while( *p && *p!='\n' ) p++; | ||
422 | if(*p) p++; | ||
423 | if(*p) item->text.push_back(p); | ||
424 | else break; | ||
425 | } | ||
426 | int firstrow = 0; | ||
427 | if(item!=items.begin()) { | ||
428 | item->top = (item-1)->top + (item-1)->text.size(); | ||
429 | } | ||
430 | else | ||
431 | item->top = 0; | ||
432 | |||
433 | firstrow = item->top; | ||
434 | if((item+1)!=items.end()) { | ||
435 | size_t newrows = item->text.size(); | ||
436 | for( item++; item!=items.end(); item++ ) item->top += newrows; | ||
437 | } | ||
438 | itemScroll.invalidate( firstrow, size() ); | ||
439 | itemScroll.changedSize(); | ||
440 | wrefresh(win); | ||
441 | } | ||
442 | |||
443 | void DynmenuCursesView::itemChanged(Dynmenu *menu, int index, const char *text) | ||
444 | { | ||
445 | std::vector<Item>::iterator item = items.begin() + index; | ||
446 | size_t oldsize = item->text.size(); | ||
447 | int lastrow = items.back().top + items.back().text.size(); | ||
448 | item->text.clear(); | ||
449 | const char *p = text; | ||
450 | item->text.push_back(p); | ||
451 | for(;;) { | ||
452 | while( *p && *p!='\n' ) p++; | ||
453 | if(*p) p++; | ||
454 | if(*p) item->text.push_back(p); | ||
455 | else break; | ||
456 | } | ||
457 | int firstrow = item->top; | ||
458 | int diff = item->text.size() - oldsize; | ||
459 | if( diff ) { | ||
460 | for( item ++; item!=items.end(); item++ ) item->top += diff; | ||
461 | item = items.end() - 1; | ||
462 | } | ||
463 | else lastrow = item->top + oldsize; | ||
464 | if( lastrow < item->top + item->text.size() ) lastrow = item->top + item->text.size(); | ||
465 | |||
466 | itemScroll.invalidate( firstrow, lastrow ); | ||
467 | itemScroll.changedSize(); | ||
468 | wrefresh(win); | ||
469 | } | ||
470 | |||
471 | void getTextExtents(const char *text, int &width, int &height ) | ||
472 | { | ||
473 | width = 0; | ||
474 | height = 0; | ||
475 | const char *p; | ||
476 | const char *s; | ||
477 | p = text; | ||
478 | do { | ||
479 | s = p; | ||
480 | while( *p && *p!='\n' ) p++; | ||
481 | if(p-s>0) { | ||
482 | height++; | ||
483 | if(width<p-s) width=p-s; | ||
484 | } else if (*p) height++; | ||
485 | if(*p) p++; | ||
486 | } while( *p ); | ||
487 | } | ||
488 | |||
489 | void drawText( WINDOW *win, const TextBox &b, const char *text, const bool center = false ) | ||
490 | { | ||
491 | int cx,cy; | ||
492 | const char *p; | ||
493 | const char *s; | ||
494 | cx = b.x; | ||
495 | cy = b.y; | ||
496 | p = text; | ||
497 | do { | ||
498 | s = p; | ||
499 | while( *p && *p!='\n' ) p++; | ||
500 | int len = p-s; | ||
501 | if(len && cy<b.y+b.h ) { | ||
502 | int offset = center ? (b.w - cx - len) / 2 : 0; | ||
503 | int res = mvwaddnstr( win, cy, cx + offset, s, len); | ||
504 | } | ||
505 | cy++; | ||
506 | if(*p) p++; | ||
507 | } while( *p ); | ||
508 | } | ||
509 | |||
510 | void clearBox( WINDOW *win, const TextBox &b ) | ||
511 | { | ||
512 | if(b.w==0 || b.h==0 ) return; | ||
513 | dprintf("clearing: h=%i, w=%i, y=%i, x=%i", b.h, b.w, b.y,b.x); | ||
514 | WINDOW *w = derwin(win,b.h,b.w,b.y,b.x); | ||
515 | werase(w); | ||
516 | touchwin(win); | ||
517 | wrefresh(w); | ||
518 | delwin(w); | ||
519 | } | ||
520 | |||
521 | void DynmenuCursesView::notify(Dynmenu *menu, Dynmenu::notify_id id) | ||
522 | { | ||
523 | int tx,ty, tw,th, oldbottom; | ||
524 | switch(id) { | ||
525 | case Dynmenu::TITLE: | ||
526 | getTextExtents( menu->title(), tw, th ); | ||
527 | tx = 0; | ||
528 | ty = 0; | ||
529 | tw = maxx - tx; | ||
530 | dprintf("tx = %i, ty=%i, title=%s, tw=%i", tx, ty, menu->title(), tw ); | ||
531 | clearBox(win,titleBox); | ||
532 | oldbottom = titleBox.y+titleBox.h; | ||
533 | titleBox.update(tx,ty,tw,th); | ||
534 | if(oldbottom!=titleBox.y+titleBox.h) { | ||
535 | notify(menu,Dynmenu::WELCOMETEXT); | ||
536 | } | ||
537 | drawText(win,titleBox, menu->title(), true ); | ||
538 | break; | ||
539 | case Dynmenu::WELCOMETEXT: | ||
540 | getTextExtents( menu->welcomeText(), tw, th ); | ||
541 | tx = ( maxx - tw )/ 2; | ||
542 | ty = titleBox.y+titleBox.h+2; | ||
543 | clearBox(win,welcomeBox); | ||
544 | if(ty+th!=welcomeBox.y+welcomeBox.h) { | ||
545 | // Need to resize itemsBox | ||
546 | if(itemwin) { | ||
547 | werase(itemwin); | ||
548 | delwin(itemwin); | ||
549 | } | ||
550 | itemwin = derwin(win,maxy-ty-th-1,maxx-3, ty+th, 2); | ||
551 | itemScroll.setWin(itemwin); | ||
552 | } | ||
553 | welcomeBox.update(tx,ty,tw,th); | ||
554 | drawText(win,welcomeBox, menu->welcomeText() ); | ||
555 | //mvwaddstr(win,ty,tx, menu->welcomeText() ); | ||
556 | break; | ||
557 | case Dynmenu::SELECTION: | ||
558 | //dprintf("items=%zu, selection=%i", menu->items.size(), menu->selection() ); | ||
559 | int sel=selected; | ||
560 | selected = menu->selection(); | ||
561 | if(sel!=-1) | ||
562 | itemScroll.invalidate(items[sel].top, items[sel].top+items[sel].text.size() ); | ||
563 | sel = selected; | ||
564 | if(sel!=-1) { | ||
565 | itemScroll.invalidate(items[sel].top, items[sel].top+items[sel].text.size() ); | ||
566 | itemScroll.scrollTo(items[sel].top, items[sel].top+items[sel].text.size() ); | ||
567 | wrefresh(itemwin); | ||
568 | } | ||
569 | break; | ||
570 | } | ||
571 | wrefresh(win); | ||
572 | } | ||
573 | |||
574 | void DynmenuCursesView::show(WINDOW *win) | ||
575 | { | ||
576 | if( win ) { | ||
577 | this->win = win; | ||
578 | getmaxyx(win,maxy,maxx); | ||
579 | //box(win, 0, 0); | ||
580 | if(itemwin) { wrefresh(itemwin); } | ||
581 | wrefresh(win); | ||
582 | } | ||
583 | else { | ||
584 | } | ||
585 | } | ||
586 | |||
587 | #include <string> | ||
588 | #include <cctype> | ||
589 | |||
590 | using std::string; | ||
591 | using std::isspace; | ||
592 | |||
593 | class Parser | ||
594 | { | ||
595 | int peeked; | ||
596 | void *gdata; | ||
597 | int (*getter)(void *); | ||
598 | int peek() { return peeked==-2 ? (peeked=getter(gdata)) : peeked; } | ||
599 | int get() { int retv = peeked==-2? (peeked=getter(gdata)) : peeked; peeked=-2; return retv;} | ||
600 | bool peekedSpace() { return isspace(peek()); } | ||
601 | void skipSpace() { while(peekedSpace()) get(); } | ||
602 | public: | ||
603 | enum tokentype { Identifier, String, EOL }; | ||
604 | enum exception { NoGetter, SyntaxError }; | ||
605 | void setCharGetter( int (*g)(void*), void *user ) { getter=g; gdata=user; } | ||
606 | void expect( tokentype t, std::string &s ); | ||
607 | Parser() : peeked(-2), getter(NULL), gdata(NULL) {} | ||
608 | }; | ||
609 | |||
610 | void Parser::expect( tokentype t, std::string &s ) | ||
611 | { | ||
612 | if(! getter ) throw NoGetter; | ||
613 | |||
614 | int c; | ||
615 | |||
616 | switch( t ) { | ||
617 | case Identifier: | ||
618 | skipSpace(); | ||
619 | s = ""; | ||
620 | while( peek()!=-1 && ! peekedSpace() ) s += (char)get() ; | ||
621 | break; | ||
622 | case String: | ||
623 | skipSpace(); | ||
624 | s = ""; | ||
625 | if( peek() !='"' ) throw SyntaxError; | ||
626 | get(); // peel off open-quote | ||
627 | while(peek()!='"' && peek()!=-1 ) { | ||
628 | c = get(); | ||
629 | if( c=='\\') { | ||
630 | c=get(); // allow escaped quotes | ||
631 | if(c=='n') c='\n'; | ||
632 | else if(c=='t') c='\t'; | ||
633 | } | ||
634 | s += (char)c ; | ||
635 | } | ||
636 | get(); // peel off close-quote | ||
637 | break; | ||
638 | case EOL: | ||
639 | while( peek()!=-1 && isspace(peek()) && peek()!='\n' ) get(); | ||
640 | if( !isspace(peek()) ) throw SyntaxError; | ||
641 | break; | ||
642 | } | ||
643 | } | ||
644 | |||
645 | |||
646 | |||
647 | |||
648 | #include <sys/select.h> | ||
649 | #include <errno.h> | ||
650 | #include <fcntl.h> | ||
651 | #include <stdio.h> | ||
652 | |||
653 | #define CTRLL 12 | ||
654 | |||
655 | |||
656 | int buf_getc( const char **p ) | ||
657 | { | ||
658 | if(**p) return *((*p)++); | ||
659 | else return -1; | ||
660 | } | ||
661 | |||
662 | void trap_signals() | ||
663 | { | ||
664 | struct sigaction s; | ||
665 | s.sa_handler = SIG_IGN; | ||
666 | int sig[] = { SIGINT, SIGTSTP, SIGQUIT, }; | ||
667 | for (int i=0; i<sizeof(sig)/sizeof(sig[0]); ++i) | ||
668 | sigaction(sig[i], &s, 0); | ||
669 | } | ||
670 | |||
671 | void bye(void) { if (!isendwin()) endwin(); } | ||
672 | |||
673 | int main(int argc, char **argv) | ||
674 | { | ||
675 | // char buf1[3]; strcpy(buf1, "joehere"); printf("buf=\"%s\"\n"); | ||
676 | if (argc != 2) { | ||
677 | fputs("usage: dynmenu socket-file\n", stderr); | ||
678 | return 1; | ||
679 | } | ||
680 | |||
681 | trap_signals(); | ||
682 | |||
683 | Dynmenu menu; | ||
684 | |||
685 | //#define SIMPLE | ||
686 | #ifdef SIMPLE | ||
687 | DynmenuSimpleView view; | ||
688 | #else | ||
689 | DynmenuCursesView view; | ||
690 | WINDOW *win = initscr(); | ||
691 | atexit(bye); | ||
692 | curs_set(0); cbreak(); noecho(); keypad(stdscr, 1); | ||
693 | wclear(win); | ||
694 | view.show(win); | ||
695 | #endif | ||
696 | menu.registerView( &view ); | ||
697 | int fd; | ||
698 | |||
699 | fd_set fds; | ||
700 | int res; | ||
701 | if ((fd = open((const char *) argv[1], O_RDWR|O_NONBLOCK)) < 0) { | ||
702 | perror("error opening socket-file"); | ||
703 | return 1; | ||
704 | } | ||
705 | FILE *fifo; | ||
706 | if ((fifo = fdopen(fd, "r")) == NULL) { | ||
707 | perror("error opening socket-file: fdopen"); | ||
708 | return 1; | ||
709 | } | ||
710 | |||
711 | FD_ZERO(&fds); FD_SET(fd, &fds); FD_SET(0, &fds); | ||
712 | top: | ||
713 | /* Note: On terminal resize, select returns -1 and sets errno to EINTR */ | ||
714 | dprintf("mainloop..."); | ||
715 | while ((res = select(fd+1, &fds, NULL, NULL, NULL)) >= 0) | ||
716 | { | ||
717 | if (FD_ISSET(0, &fds)) { | ||
718 | //dprintf("char !");//space!"); | ||
719 | int c = getch(); | ||
720 | if (c == ERR) { | ||
721 | perror("error reading stdin"); | ||
722 | return 1; | ||
723 | } | ||
724 | switch (c) { | ||
725 | case KEY_UP: case 'k': menu.cursorUp(); break; | ||
726 | case KEY_DOWN: case 'j': menu.cursorDown(); break; | ||
727 | case KEY_RIGHT: case '\n': case '\x0d': case 'l': | ||
728 | case KEY_ENTER: | ||
729 | if (!menu.haveSelection()) | ||
730 | break; | ||
731 | bye(); | ||
732 | endwin(); | ||
733 | execl("/bin/sh", "/bin/sh", "-c", menu.selectedCommand(), (char *) NULL); | ||
734 | perror("exec failed"); | ||
735 | exit(1); | ||
736 | break; | ||
737 | |||
738 | case 'Q': bye(); endwin(); exit(0); break; | ||
739 | |||
740 | #ifndef SIMPLE | ||
741 | case 'r': | ||
742 | case CTRLL: menu.redraw(); break; | ||
743 | #endif | ||
744 | /* | ||
745 | case ' ': | ||
746 | { | ||
747 | dprintf("space!"); | ||
748 | static char b = 'A'; | ||
749 | char buf[80]; sprintf( buf, "Letter '%c'", b++ ); | ||
750 | menu.setItem( "id3", "3", buf, "id3 cmd"); | ||
751 | } | ||
752 | */ | ||
753 | break; | ||
754 | } | ||
755 | } | ||
756 | if (FD_ISSET(fd, &fds)) { | ||
757 | Parser *p = new Parser; | ||
758 | char line[1024];//[PIPE_BUF]; | ||
759 | assert(line); | ||
760 | char *pointer = line; | ||
761 | while (fgets(line, 1024, fifo) != NULL) { | ||
762 | dprintf("%s", line ); | ||
763 | try { | ||
764 | pointer = line; | ||
765 | string menuMethod(1024,' '); | ||
766 | string id(1024, ' '); string sortkey(1024, ' '); | ||
767 | string desc(1024, ' '); string cmd(1024, ' '); string dummy; | ||
768 | p->setCharGetter( (int (*)(void*))buf_getc, (void*)&pointer ); | ||
769 | p->expect( Parser::Identifier, menuMethod ); | ||
770 | if( menuMethod=="setItem" ) { | ||
771 | p->expect( Parser::String, id ); | ||
772 | p->expect( Parser::String, sortkey ); | ||
773 | p->expect( Parser::String, desc ); | ||
774 | p->expect( Parser::String, cmd ); | ||
775 | menu.setItem( id.c_str(), sortkey.c_str(), desc.c_str(), cmd.c_str() ); | ||
776 | } | ||
777 | else if( menuMethod=="setTitle" ) { | ||
778 | p->expect( Parser::String, desc ); | ||
779 | menu.setTitle( desc.c_str() ); | ||
780 | } | ||
781 | else if( menuMethod=="setWelcomeText" ) { | ||
782 | p->expect( Parser::String, desc ); | ||
783 | menu.setWelcomeText( desc.c_str() ); | ||
784 | } | ||
785 | else if( menuMethod=="delItem" ) { | ||
786 | p->expect( Parser::String, id ); | ||
787 | menu.delItem( id.c_str() ); | ||
788 | } | ||
789 | else if( menuMethod=="selectItem" ) { | ||
790 | p->expect( Parser::String, id ); | ||
791 | menu.selectItem( id.c_str() ); | ||
792 | } | ||
793 | } | ||
794 | catch(...) { | ||
795 | dprintf("exception"); | ||
796 | } | ||
797 | } | ||
798 | } | ||
799 | FD_ZERO(&fds); | ||
800 | if (!feof(fifo)) // will never eof if it is a fifo | ||
801 | FD_SET(fd, &fds); // could use inotify here... | ||
802 | FD_SET(0, &fds); | ||
803 | } | ||
804 | char ebuf[80] =""; | ||
805 | switch(errno) | ||
806 | { | ||
807 | case EINTR: | ||
808 | menu.redraw(); | ||
809 | goto top; | ||
810 | case EBADF: strcpy(ebuf, "EBADF"); break; | ||
811 | case EINVAL: strcpy(ebuf, "EINVAL"); break; | ||
812 | case ENOMEM: strcpy(ebuf, "ENOMEM"); break; | ||
813 | default: sprintf(ebuf, "0x%X", errno); | ||
814 | } | ||
815 | endwin(); | ||
816 | printf(" errno = %s\n", ebuf ); | ||
817 | |||
818 | return 0; | ||
819 | } | ||
820 | |||
821 | // vim:ts=2 sw=2 et: | ||