more changes for service groups (replacement for zapping alternatives
[enigma2.git] / lib / gui / elistboxcontent.cpp
1 #include <lib/gui/elistbox.h>
2 #include <lib/gui/elistboxcontent.h>
3 #include <lib/gdi/font.h>
4 #include <lib/python/python.h>
5
6 /*
7     The basic idea is to have an interface which gives all relevant list
8     processing functions, and can be used by the listbox to browse trough
9     the list.
10     
11     The listbox directly uses the implemented cursor. It tries hard to avoid
12     iterating trough the (possibly very large) list, so it should be O(1),
13     i.e. the performance should not be influenced by the size of the list.
14     
15     The list interface knows how to draw the current entry to a specified 
16     offset. Different interfaces can be used to adapt different lists,
17     pre-filter lists on the fly etc.
18     
19                 cursorSave/Restore is used to avoid re-iterating the list on redraw.
20                 The current selection is always selected as cursor position, the
21     cursor is then positioned to the start, and then iterated. This gives
22     at most 2x m_items_per_page cursor movements per redraw, indepenent
23     of the size of the list.
24     
25     Although cursorSet is provided, it should be only used when there is no
26     other way, as it involves iterating trough the list.
27  */
28
29 iListboxContent::~iListboxContent()
30 {
31 }
32
33 iListboxContent::iListboxContent(): m_listbox(0)
34 {
35 }
36
37 void iListboxContent::setListbox(eListbox *lb)
38 {
39         m_listbox = lb;
40 }
41
42 int iListboxContent::currentCursorSelectable()
43 {
44         return 1;
45 }
46
47 //////////////////////////////////////
48
49 DEFINE_REF(eListboxPythonStringContent);
50
51 eListboxPythonStringContent::eListboxPythonStringContent()
52 {
53 }
54
55 eListboxPythonStringContent::~eListboxPythonStringContent()
56 {
57         Py_XDECREF(m_list);
58 }
59
60 void eListboxPythonStringContent::cursorHome()
61 {
62         m_cursor = 0;
63 }
64
65 void eListboxPythonStringContent::cursorEnd()
66 {
67         m_cursor = size();
68 }
69
70 int eListboxPythonStringContent::cursorMove(int count)
71 {
72         m_cursor += count;
73         
74         if (m_cursor < 0)
75                 cursorHome();
76         else if (m_cursor > size())
77                 cursorEnd();
78         return 0;
79 }
80
81 int eListboxPythonStringContent::cursorValid()
82 {
83         return m_cursor < size();
84 }
85
86 int eListboxPythonStringContent::cursorSet(int n)
87 {
88         m_cursor = n;
89         
90         if (m_cursor < 0)
91                 cursorHome();
92         else if (m_cursor > size())
93                 cursorEnd();
94         return 0;
95 }
96
97 int eListboxPythonStringContent::cursorGet()
98 {
99         return m_cursor;
100 }
101
102 int eListboxPythonStringContent::currentCursorSelectable()
103 {
104         if (m_list && cursorValid())
105         {
106                 ePyObject item = PyList_GET_ITEM(m_list, m_cursor);
107                 if (!PyTuple_Check(item))
108                         return 1;
109                 if (PyTuple_Size(item) >= 2)
110                         return 1;
111         }
112         return 0;
113 }
114
115 void eListboxPythonStringContent::cursorSave()
116 {
117         m_saved_cursor = m_cursor;
118 }
119
120 void eListboxPythonStringContent::cursorRestore()
121 {
122         m_cursor = m_saved_cursor;
123 }
124
125 int eListboxPythonStringContent::size()
126 {
127         if (!m_list)
128                 return 0;
129         return PyList_Size(m_list);
130 }
131         
132 void eListboxPythonStringContent::setSize(const eSize &size)
133 {
134         m_itemsize = size;
135 }
136
137 void eListboxPythonStringContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
138 {
139         ePtr<gFont> fnt = new gFont("Regular", 20);
140         painter.clip(eRect(offset, m_itemsize));
141         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
142         painter.clear();
143
144         if (m_list && cursorValid())
145         {
146                 int gray = 0;
147                 ePyObject item = PyList_GET_ITEM(m_list, m_cursor); // borrowed reference!
148                 painter.setFont(fnt);
149
150                         /* the user can supply tuples, in this case the first one will be displayed. */         
151                 if (PyTuple_Check(item))
152                 {
153                         if (PyTuple_Size(item) == 1)
154                                 gray = 1;
155                         item = PyTuple_GET_ITEM(item, 0);
156                 }
157                 
158                 if (item == Py_None)
159                 {
160                         int half_height = m_itemsize.height() / 2;
161                         
162                         painter.fill(eRect(offset.x() + half_height, offset.y() + half_height - 2, m_itemsize.width() - m_itemsize.height(), 4));
163                 } else
164                 {
165                         const char *string = PyString_Check(item) ? PyString_AsString(item) : "<not-a-string>";
166                         ePoint text_offset = offset + (selected ? ePoint(2, 2) : ePoint(1, 1));
167                         if (gray)
168                                 painter.setForegroundColor(gRGB(0x808080));
169                         painter.renderText(eRect(text_offset, m_itemsize), string);
170                 }
171                 
172                 if (selected)
173                         style.drawFrame(painter, eRect(offset, m_itemsize), eWindowStyle::frameListboxEntry);
174         }
175         
176         painter.clippop();
177 }
178
179 void eListboxPythonStringContent::setList(ePyObject list)
180 {
181         Py_XDECREF(m_list);
182         if (!PyList_Check(list))
183         {
184                 m_list = ePyObject();
185         } else
186         {
187                 m_list = list;
188                 Py_INCREF(m_list);
189         }
190
191         if (m_listbox)
192                 m_listbox->entryReset(false);
193 }
194
195 PyObject *eListboxPythonStringContent::getCurrentSelection()
196 {
197         if (!(m_list && cursorValid()))
198         {
199                 Py_INCREF(Py_None);
200                 return Py_None;
201         }
202         ePyObject r = PyList_GET_ITEM(m_list, m_cursor);
203         Py_XINCREF(r);
204         return r;
205 }
206
207 void eListboxPythonStringContent::invalidateEntry(int index)
208 {
209         if (m_listbox)
210                 m_listbox->entryChanged(index);
211 }
212
213 void eListboxPythonStringContent::invalidate()
214 {
215         if (m_listbox)
216         {
217                 int s = size();
218                 if ( m_cursor >= s )
219                         m_listbox->moveSelectionTo(s?s-1:0);
220                 m_listbox->invalidate();
221         }
222 }
223
224 //////////////////////////////////////
225
226 void eListboxPythonConfigContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
227 {
228         ePtr<gFont> fnt = new gFont("Regular", 20);
229         ePtr<gFont> fnt2 = new gFont("Regular", 16);
230         eRect itemrect(offset, m_itemsize);
231         painter.clip(itemrect);
232         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
233         painter.clear();
234
235         if (m_list && cursorValid())
236         {
237                         /* get current list item */
238                 ePyObject item = PyList_GET_ITEM(m_list, m_cursor); // borrowed reference!
239                 ePyObject text, value;
240                 painter.setFont(fnt);
241
242                         /* the first tuple element is a string for the left side.
243                            the second one will be called, and the result shall be an tuple.
244                            
245                            of this tuple,
246                            the first one is the type (string).
247                            the second one is the value. */
248                 if (PyTuple_Check(item))
249                 {
250                                 /* handle left part. get item from tuple, convert to string, display. */
251                                 
252                         text = PyTuple_GET_ITEM(item, 0);
253                         text = PyObject_Str(text); /* creates a new object - old object was borrowed! */
254                         const char *string = (text && PyString_Check(text)) ? PyString_AsString(text) : "<not-a-string>";
255                         eSize item_left = eSize(m_seperation, m_itemsize.height());
256                         eSize item_right = eSize(m_itemsize.width() - m_seperation, m_itemsize.height());
257                         painter.renderText(eRect(offset, item_left), string, gPainter::RT_HALIGN_LEFT);
258                         Py_XDECREF(text);
259                         
260                                 /* when we have no label, align value to the left. (FIXME: 
261                                    don't we want to specifiy this individually?) */
262                         int value_alignment_left = !*string;
263                         
264                                 /* now, handle the value. get 2nd part from tuple*/
265                         value = PyTuple_GET_ITEM(item, 1);
266                         if (value)
267                         {
268                                 ePyObject args = PyTuple_New(1);
269                                 PyTuple_SET_ITEM(args, 0, PyInt_FromLong(selected));
270                                 
271                                         /* CallObject will call __call__ which should return the value tuple */
272                                 value = PyObject_CallObject(value, args);
273                                 
274                                 if (PyErr_Occurred())
275                                         PyErr_Print();
276
277                                 Py_DECREF(args);
278                                         /* the PyInt was stolen. */
279                         }
280                         
281                                 /*  check if this is really a tuple */
282                         if (value && PyTuple_Check(value))
283                         {
284                                         /* convert type to string */
285                                 ePyObject type = PyTuple_GET_ITEM(value, 0);
286                                 const char *atype = (type && PyString_Check(type)) ? PyString_AsString(type) : 0;
287                                 
288                                 if (atype)
289                                 {
290                                         if (!strcmp(atype, "text"))
291                                         {
292                                                 ePyObject pvalue = PyTuple_GET_ITEM(value, 1);
293                                                 const char *value = (pvalue && PyString_Check(pvalue)) ? PyString_AsString(pvalue) : "<not-a-string>";
294                                                 painter.setFont(fnt2);
295                                                 if (value_alignment_left)
296                                                         painter.renderText(eRect(offset, item_right), value, gPainter::RT_HALIGN_LEFT);
297                                                 else
298                                                         painter.renderText(eRect(offset + eSize(m_seperation, 0), item_right), value, gPainter::RT_HALIGN_RIGHT);
299
300                                                         /* pvalue is borrowed */
301                                         } else if (!strcmp(atype, "slider"))
302                                         {
303                                                 ePyObject pvalue = PyTuple_GET_ITEM(value, 1);
304                                                 ePyObject psize = PyTuple_GET_ITEM(value, 2);
305                                                 
306                                                         /* convert value to Long. fallback to -1 on error. */
307                                                 int value = (pvalue && PyInt_Check(pvalue)) ? PyInt_AsLong(pvalue) : -1;
308                                                 int size = (pvalue && PyInt_Check(psize)) ? PyInt_AsLong(psize) : 100;
309                                                 
310                                                         /* calc. slider length */
311                                                 int width = item_right.width() * value / size;
312                                                 int height = item_right.height();
313                                                 
314                                                                                                 
315                                                         /* draw slider */
316                                                 //painter.fill(eRect(offset.x() + m_seperation, offset.y(), width, height));
317                                                 //hack - make it customizable
318                                                 painter.fill(eRect(offset.x() + m_seperation, offset.y() + 5, width, height-10));
319                                                 
320                                                         /* pvalue is borrowed */
321                                         } else if (!strcmp(atype, "mtext"))
322                                         {
323                                                 ePyObject pvalue = PyTuple_GET_ITEM(value, 1);
324                                                 const char *text = (pvalue && PyString_Check(pvalue)) ? PyString_AsString(pvalue) : "<not-a-string>";
325                                                 int xoffs = value_alignment_left ? 0 : m_seperation;
326                                                 ePtr<eTextPara> para = new eTextPara(eRect(offset + eSize(xoffs, 0), item_right));
327                                                 para->setFont(fnt2);
328                                                 para->renderString(text, 0);
329                                                 para->realign(value_alignment_left ? eTextPara::dirLeft : eTextPara::dirRight);
330                                                 int glyphs = para->size();
331                                                 
332                                                 ePyObject plist;
333                                                 
334                                                 if (PyTuple_Size(value) >= 3)
335                                                         plist = PyTuple_GET_ITEM(value, 2);
336                                                 
337                                                 int entries = 0;
338
339                                                 if (plist && PyList_Check(plist))
340                                                         entries = PyList_Size(plist);
341                                                 
342                                                 for (int i = 0; i < entries; ++i)
343                                                 {
344                                                         ePyObject entry = PyList_GET_ITEM(plist, i);
345                                                         int num = PyInt_Check(entry) ? PyInt_AsLong(entry) : -1;
346                                                         
347                                                         if ((num < 0) || (num >= glyphs))
348                                                                 eWarning("glyph index %d in PythonConfigList out of bounds!", num);
349                                                         else
350                                                         {
351                                                                 para->setGlyphFlag(num, GS_INVERT);
352                                                                 eRect bbox;
353                                                                 bbox = para->getGlyphBBox(num);
354                                                                 bbox = eRect(bbox.left(), offset.y(), bbox.width(), m_itemsize.height());
355                                                                 painter.fill(bbox);
356                                                         }
357                                                                 /* entry is borrowed */
358                                                 }
359                                                 
360                                                 painter.renderPara(para, ePoint(0, 0));
361                                                         /* pvalue is borrowed */
362                                                         /* plist is 0 or borrowed */
363                                         }
364                                 }
365                                         /* type is borrowed */
366                         } else
367                                 eWarning("eListboxPythonConfigContent: second value of tuple is not a tuple.");
368                                 /* value is borrowed */
369                 }
370
371                 if (selected)
372                         style.drawFrame(painter, eRect(offset, m_itemsize), eWindowStyle::frameListboxEntry);
373         }
374         
375         painter.clippop();
376 }
377
378 int eListboxPythonConfigContent::currentCursorSelectable()
379 {
380         return eListboxPythonStringContent::currentCursorSelectable();
381 }
382
383 //////////////////////////////////////
384
385         /* todo: make a real infrastructure here! */
386 RESULT SwigFromPython(ePtr<gPixmap> &res, PyObject *obj);
387
388 eListboxPythonMultiContent::eListboxPythonMultiContent()
389 {
390 }
391
392 eListboxPythonMultiContent::~eListboxPythonMultiContent()
393 {
394         if (m_buildFunc)
395                 Py_DECREF(m_buildFunc);
396 }
397
398 void eListboxPythonMultiContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
399 {
400         eRect itemrect(offset, m_itemsize);
401         painter.clip(itemrect);
402         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
403         painter.clear();
404
405         ePyObject items;
406
407         if (m_list && cursorValid())
408         {
409                 items = PyList_GET_ITEM(m_list, m_cursor); // borrowed reference!
410
411                 if (m_buildFunc)
412                 {
413                         if (PyCallable_Check(m_buildFunc))  // when we have a buildFunc then call it
414                         {
415                                 if (PyTuple_Check(items))
416                                         items = PyObject_CallObject(m_buildFunc, items);
417                                 else
418                                         eDebug("items is no tuple");
419                         }
420                         else
421                                 eDebug("buildfunc is not callable");
422                 }
423
424                 if (!items)
425                 {
426                         eDebug("eListboxPythonMultiContent: error getting item %d", m_cursor);
427                         goto error_out;
428                 }
429                 
430                 if (!PyList_Check(items))
431                 {
432                         eDebug("eListboxPythonMultiContent: list entry %d is not a list", m_cursor);
433                         goto error_out;
434                 }
435                 
436                 int size = PyList_Size(items);
437                 for (int i = 1; i < size; ++i)
438                 {
439                         ePyObject item = PyList_GET_ITEM(items, i); // borrowed reference!
440                         
441                         if (!item)
442                         {
443                                 eDebug("eListboxPythonMultiContent: ?");
444                                 goto error_out;
445                         }
446                         
447                         ePyObject px, py, pwidth, pheight, pfnt, pstring, pflags, pcolor;
448                 
449                         /*
450                                 we have a list of tuples:
451                                 
452                                 (0, x, y, width, height, fnt, flags, "bla"[, color] ),
453
454                                 or, for a progress:
455                                 (1, x, y, width, height, filled_percent )
456
457                                 or, for a pixmap:
458                                 
459                                 (2, x, y, width, height, pixmap )
460                                 
461                          */
462                         
463                         if (!PyTuple_Check(item))
464                         {
465                                 eDebug("eListboxPythonMultiContent did not receive a tuple.");
466                                 goto error_out;
467                         }
468
469                         int size = PyTuple_Size(item);
470
471                         if (!size)
472                         {
473                                 eDebug("eListboxPythonMultiContent receive empty tuple.");
474                                 goto error_out;
475                         }
476
477                         int type = PyInt_AsLong(PyTuple_GET_ITEM(item, 0));
478
479                         if (size > 5)
480                         {
481                                 px = PyTuple_GET_ITEM(item, 1);
482                                 py = PyTuple_GET_ITEM(item, 2);
483                                 pwidth = PyTuple_GET_ITEM(item, 3);
484                                 pheight = PyTuple_GET_ITEM(item, 4);
485                                 pfnt = PyTuple_GET_ITEM(item, 5); /* could also be an pixmap or an int (progress filled percent) */
486                                 if (size > 7)
487                                 {
488                                         pflags = PyTuple_GET_ITEM(item, 6);
489                                         pstring = PyTuple_GET_ITEM(item, 7);
490                                 }
491                                 if (size > 8)
492                                         pcolor = PyTuple_GET_ITEM(item, 8);
493                         }
494                         
495                         switch (type)
496                         {
497                         case TYPE_TEXT: // text
498                         {
499                                 if (!(px && py && pwidth && pheight && pfnt && pstring))
500                                 {
501                                         eDebug("eListboxPythonMultiContent received too small tuple (must be (TYPE_TEXT, x, y, width, height, fnt, flags, string, [color, ]...])");
502                                         goto error_out;
503                                 }
504                                 
505                                 const char *string = (PyString_Check(pstring)) ? PyString_AsString(pstring) : "<not-a-string>";
506                                 int x = PyInt_AsLong(px);
507                                 int y = PyInt_AsLong(py);
508                                 int width = PyInt_AsLong(pwidth);
509                                 int height = PyInt_AsLong(pheight);
510                                 int flags = PyInt_AsLong(pflags);
511                                 int fnt = PyInt_AsLong(pfnt);
512                                 
513                                 if (pcolor)
514                                 {
515                                         int color = PyInt_AsLong(pcolor);
516                                         painter.setForegroundColor(gRGB(color));
517                                 }
518                                 
519                                 if (m_font.find(fnt) == m_font.end())
520                                 {
521                                         eDebug("eListboxPythonMultiContent: specified font %d was not found!", fnt);
522                                         goto error_out;
523                                 }
524                                 
525                                 eRect r = eRect(x, y, width, height);
526                                 r.moveBy(offset);
527                                 r &= itemrect;
528                                 
529                                 painter.setFont(m_font[fnt]);
530                                 
531                                 painter.clip(r);
532                                 painter.renderText(r, string, flags);
533                                 painter.clippop();
534                                 break;
535                         }
536                         case TYPE_PROGRESS: // Progress
537                         {
538                                 if (!(px && py && pwidth && pheight && pfnt))
539                                 {
540                                         eDebug("eListboxPythonMultiContent received too small tuple (must be (TYPE_PROGRESS, x, y, width, height, filled percent))");
541                                         goto error_out;
542                                 }
543                                 int x = PyInt_AsLong(px);
544                                 int y = PyInt_AsLong(py);
545                                 int width = PyInt_AsLong(pwidth);
546                                 int height = PyInt_AsLong(pheight);
547                                 int filled = PyInt_AsLong(pfnt);
548
549                                 eRect r = eRect(x, y, width, height);
550                                 r.moveBy(offset);
551                                 r &= itemrect;
552
553                                 painter.clip(r);
554                                 int bwidth=2;  // borderwidth hardcoded yet
555
556                                 // border
557                                 eRect rc = eRect(x, y, width, bwidth);
558                                 rc.moveBy(offset);
559                                 painter.fill(rc);
560
561                                 rc = eRect(x, y+bwidth, bwidth, height-bwidth);
562                                 rc.moveBy(offset);
563                                 painter.fill(rc);
564
565                                 rc = eRect(x+bwidth, y+height-bwidth, width-bwidth, bwidth);
566                                 rc.moveBy(offset);
567                                 painter.fill(rc);
568
569                                 rc = eRect(x+width-bwidth, y+bwidth, bwidth, height-bwidth);
570                                 rc.moveBy(offset);
571                                 painter.fill(rc);
572
573                                 // progress
574                                 rc = eRect(x+bwidth, y+bwidth, (width-bwidth*2) * filled / 100, height-bwidth*2);
575                                 rc.moveBy(offset);
576                                 painter.fill(rc);
577
578                                 painter.clippop();
579
580                                 break;
581                         }
582                         case TYPE_PIXMAP_ALPHATEST:
583                         case TYPE_PIXMAP: // pixmap
584                         {
585                                 if (!(px && py && pwidth && pheight && pfnt))
586                                 {
587                                         eDebug("eListboxPythonMultiContent received too small tuple (must be (TYPE_PIXMAP, x, y, width, height, pixmap))");
588                                         goto error_out;
589                                 }
590                                 int x = PyInt_AsLong(px);
591                                 int y = PyInt_AsLong(py);
592                                 int width = PyInt_AsLong(pwidth);
593                                 int height = PyInt_AsLong(pheight);
594                                 ePtr<gPixmap> pixmap;
595                                 if (SwigFromPython(pixmap, pfnt))
596                                 {
597                                         eDebug("eListboxPythonMultiContent (Pixmap) get pixmap failed");
598                                         goto error_out;
599                                 }
600
601                                 eRect r = eRect(x, y, width, height);
602                                 r.moveBy(offset);
603                                 r &= itemrect;
604                                 
605                                 painter.clip(r);
606                                 painter.blit(pixmap, r.topLeft(), r, (type == TYPE_PIXMAP_ALPHATEST) ? gPainter::BT_ALPHATEST : 0);
607                                 painter.clippop();
608
609                                 break;
610                         }
611                         default:
612                                 eWarning("eListboxPythonMultiContent received unknown type (%d)", type);
613                                 goto error_out;
614                         }
615                         
616                         if (pcolor)
617                                 style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
618                 }
619         }
620         
621         if (selected)
622                 style.drawFrame(painter, eRect(offset, m_itemsize), eWindowStyle::frameListboxEntry);
623
624 error_out:
625         if (m_buildFunc && PyCallable_Check(m_buildFunc) && items)
626                 Py_DECREF(items);
627
628         painter.clippop();
629 }
630
631 void eListboxPythonMultiContent::setBuildFunc(ePyObject cb)
632 {
633         if (m_buildFunc)
634                 Py_DECREF(m_buildFunc);
635         m_buildFunc=cb;
636         if (cb)
637                 Py_INCREF(m_buildFunc);
638 }
639
640 int eListboxPythonMultiContent::currentCursorSelectable()
641 {
642         /* each list-entry is a list of tuples. if the first of these is none, it's not selectable */
643         if (m_list && cursorValid())
644         {
645                 ePyObject item = PyList_GET_ITEM(m_list, m_cursor);
646                 if (PyList_Check(item))
647                 {
648                         item = PyList_GET_ITEM(item, 0);
649                         if (item != Py_None)
650                                 return 1;
651                 }
652                 else if (m_buildFunc && PyCallable_Check(m_buildFunc))
653                 // FIXME .. how we can detect non selectable entrys when we have a buildFunc callback
654                         return 1;
655         }
656         return 0;
657 }
658
659 void eListboxPythonMultiContent::setFont(int fnt, gFont *font)
660 {
661         if (font)
662                 m_font[fnt] = font;
663         else
664                 m_font.erase(fnt);
665 }