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