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