fdb270f80b6a0c8e89c5121ae203aba80b88907e
[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;
413                 
414                         /*
415                                 we have a list of tuples:
416                                 
417                                 (0, x, y, width, height, fnt, flags, "bla" ),
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                         }
457                         
458                         switch (type)
459                         {
460                         case TYPE_TEXT: // text
461                         {
462                                 if (!(px && py && pwidth && pheight && pfnt && pstring))
463                                 {
464                                         eDebug("eListboxPythonMultiContent received too small tuple (must be (TYPE_TEXT, x, y, width, height, fnt, flags, string[, ...])");
465                                         goto error_out;
466                                 }
467                                 
468                                 const char *string = (PyString_Check(pstring)) ? PyString_AsString(pstring) : "<not-a-string>";
469                                 int x = PyInt_AsLong(px);
470                                 int y = PyInt_AsLong(py);
471                                 int width = PyInt_AsLong(pwidth);
472                                 int height = PyInt_AsLong(pheight);
473                                 int flags = PyInt_AsLong(pflags);
474                                 int fnt = PyInt_AsLong(pfnt);
475                                 
476                                 if (m_font.find(fnt) == m_font.end())
477                                 {
478                                         eDebug("eListboxPythonMultiContent: specified font %d was not found!", fnt);
479                                         goto error_out;
480                                 }
481                                 
482                                 eRect r = eRect(x, y, width, height);
483                                 r.moveBy(offset);
484                                 r &= itemrect;
485                                 
486                                 painter.setFont(m_font[fnt]);
487                                 
488                                 painter.clip(r);
489                                 painter.renderText(r, string, flags);
490                                 painter.clippop();
491                                 break;
492                         }
493                         case TYPE_PROGRESS: // Progress
494                         {
495                                 if (!(px && py && pwidth && pheight && pfnt))
496                                 {
497                                         eDebug("eListboxPythonMultiContent received too small tuple (must be (TYPE_PROGRESS, x, y, width, height, filled percent))");
498                                         goto error_out;
499                                 }
500                                 int x = PyInt_AsLong(px);
501                                 int y = PyInt_AsLong(py);
502                                 int width = PyInt_AsLong(pwidth);
503                                 int height = PyInt_AsLong(pheight);
504                                 int filled = PyInt_AsLong(pfnt);
505
506                                 eRect r = eRect(x, y, width, height);
507                                 r.moveBy(offset);
508                                 r &= itemrect;
509
510                                 painter.clip(r);
511                                 int bwidth=2;  // borderwidth hardcoded yet
512
513                                 // border
514                                 eRect rc = eRect(x, y, width, bwidth);
515                                 rc.moveBy(offset);
516                                 painter.fill(rc);
517
518                                 rc = eRect(x, y+bwidth, bwidth, height-bwidth);
519                                 rc.moveBy(offset);
520                                 painter.fill(rc);
521
522                                 rc = eRect(x+bwidth, y+height-bwidth, width-bwidth, bwidth);
523                                 rc.moveBy(offset);
524                                 painter.fill(rc);
525
526                                 rc = eRect(x+width-bwidth, y+bwidth, bwidth, height-bwidth);
527                                 rc.moveBy(offset);
528                                 painter.fill(rc);
529
530                                 // progress
531                                 rc = eRect(x+bwidth, y+bwidth, (width-bwidth*2) * filled / 100, height-bwidth*2);
532                                 rc.moveBy(offset);
533                                 painter.fill(rc);
534
535                                 painter.clippop();
536
537                                 break;
538                         }
539                         case TYPE_PIXMAP_ALPHATEST:
540                         case TYPE_PIXMAP: // pixmap
541                         {
542                                 if (!(px && py && pwidth && pheight && pfnt))
543                                 {
544                                         eDebug("eListboxPythonMultiContent received too small tuple (must be (TYPE_PIXMAP, x, y, width, height, pixmap))");
545                                         goto error_out;
546                                 }
547                                 int x = PyInt_AsLong(px);
548                                 int y = PyInt_AsLong(py);
549                                 int width = PyInt_AsLong(pwidth);
550                                 int height = PyInt_AsLong(pheight);
551                                 ePtr<gPixmap> pixmap;
552                                 if (SwigFromPython(pixmap, pfnt))
553                                 {
554                                         eDebug("eListboxPythonMultiContent (Pixmap) get pixmap failed");
555                                         goto error_out;
556                                 }
557
558                                 eRect r = eRect(x, y, width, height);
559                                 r.moveBy(offset);
560                                 r &= itemrect;
561                                 
562                                 painter.clip(r);
563                                 painter.blit(pixmap, r.topLeft(), r, (type == TYPE_PIXMAP_ALPHATEST) ? gPainter::BT_ALPHATEST : 0);
564                                 painter.clippop();
565
566                                 break;
567                         }
568                         default:
569                                 eWarning("eListboxPythonMultiContent received unknown type (%d)", type);
570                                 goto error_out;
571                         }
572                 }
573         }
574         
575         if (selected)
576                 style.drawFrame(painter, eRect(offset, m_itemsize), eWindowStyle::frameListboxEntry);
577
578 error_out:
579         painter.clippop();
580 }
581
582 int eListboxPythonMultiContent::currentCursorSelectable()
583 {
584         return eListboxPythonStringContent::currentCursorSelectable();
585 }
586
587 void eListboxPythonMultiContent::setFont(int fnt, gFont *font)
588 {
589         if (font)
590                 m_font[fnt] = font;
591         else
592                 m_font.erase(fnt);
593 }