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