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