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