setList() will invalidate itself
[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 DEFINE_REF(eListboxTestContent);
43
44 void eListboxTestContent::cursorHome()
45 {
46         m_cursor = 0;
47 }
48
49 void eListboxTestContent::cursorEnd()
50 {
51         m_cursor = size();
52 }
53
54 int eListboxTestContent::cursorMove(int count)
55 {
56         m_cursor += count;
57         
58         if (m_cursor < 0)
59                 cursorHome();
60         else if (m_cursor > size())
61                 cursorEnd();
62         return 0;
63 }
64
65 int eListboxTestContent::cursorValid()
66 {
67         return m_cursor < size();
68 }
69
70 int eListboxTestContent::cursorSet(int n)
71 {
72         m_cursor = n;
73         
74         if (m_cursor < 0)
75                 cursorHome();
76         else if (m_cursor > size())
77                 cursorEnd();
78         return 0;
79 }
80
81 int eListboxTestContent::cursorGet()
82 {
83         return m_cursor;
84 }
85
86 void eListboxTestContent::cursorSave()
87 {
88         m_saved_cursor = m_cursor;
89 }
90
91 void eListboxTestContent::cursorRestore()
92 {
93         m_cursor = m_saved_cursor;
94 }
95
96 int eListboxTestContent::size()
97 {
98         return 10;
99 }
100         
101 RESULT eListboxTestContent::connectItemChanged(const Slot0<void> &itemChanged, ePtr<eConnection> &connection)
102 {
103         return 0;
104 }
105
106 void eListboxTestContent::setSize(const eSize &size)
107 {
108         m_size = size;
109 }
110
111 void eListboxTestContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
112 {
113         ePtr<gFont> fnt = new gFont("Arial", 20);
114         painter.clip(eRect(offset, m_size));
115         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
116         painter.clear();
117
118         if (cursorValid())
119         {
120                 painter.setFont(fnt);
121                 char string[10];
122                 sprintf(string, "%d.)", m_cursor);
123                 
124                 ePoint text_offset = offset + (selected ? ePoint(2, 2) : ePoint(1, 1));
125                 
126                 painter.renderText(eRect(text_offset, m_size), string);
127                 
128                 if (selected)
129                         style.drawFrame(painter, eRect(offset, m_size), eWindowStyle::frameListboxEntry);
130         }
131         
132         painter.clippop();
133 }
134
135 //////////////////////////////////////
136
137 DEFINE_REF(eListboxStringContent);
138
139 eListboxStringContent::eListboxStringContent()
140 {
141         m_size = 0;
142         cursorHome();
143 }
144
145 void eListboxStringContent::cursorHome()
146 {
147         m_cursor = m_list.begin();
148         m_cursor_number = 0;
149 }
150
151 void eListboxStringContent::cursorEnd()
152 {
153         m_cursor = m_list.end();
154         m_cursor_number = m_size;
155 }
156
157 int eListboxStringContent::cursorMove(int count)
158 {
159         if (count > 0)
160         {
161                 while (count && (m_cursor != m_list.end()))
162                 {
163                         ++m_cursor;
164                         ++m_cursor_number;
165                         --count;
166                 }
167         } else if (count < 0)
168         {
169                 while (count && (m_cursor != m_list.begin()))
170                 {
171                         --m_cursor;
172                         --m_cursor_number;
173                         ++count;
174                 }
175         }
176         
177         return 0;
178 }
179
180 int eListboxStringContent::cursorValid()
181 {
182         return m_cursor != m_list.end();
183 }
184
185 int eListboxStringContent::cursorSet(int n)
186 {
187         cursorHome();
188         cursorMove(n);
189         
190         return 0;
191 }
192
193 int eListboxStringContent::cursorGet()
194 {
195         return m_cursor_number;
196 }
197
198 void eListboxStringContent::cursorSave()
199 {
200         m_saved_cursor = m_cursor;
201         m_saved_cursor_number = m_cursor_number;
202 }
203
204 void eListboxStringContent::cursorRestore()
205 {
206         m_cursor = m_saved_cursor;
207         m_cursor_number = m_saved_cursor_number;
208 }
209
210 int eListboxStringContent::size()
211 {
212         return m_size;
213 }
214         
215 void eListboxStringContent::setSize(const eSize &size)
216 {
217         m_itemsize = size;
218 }
219
220 void eListboxStringContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
221 {
222         ePtr<gFont> fnt = new gFont("Arial", 20);
223         painter.clip(eRect(offset, m_itemsize));
224         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
225         painter.clear();
226         
227         eDebug("item %d", m_cursor_number);
228         if (cursorValid())
229         {
230                 eDebug("is valid..");
231                 painter.setFont(fnt);
232                 
233                 ePoint text_offset = offset + (selected ? ePoint(2, 2) : ePoint(1, 1));
234                 
235                 painter.renderText(eRect(text_offset, m_itemsize), *m_cursor);
236                 
237                 if (selected)
238                         style.drawFrame(painter, eRect(offset, m_itemsize), eWindowStyle::frameListboxEntry);
239         }
240         
241         painter.clippop();
242 }
243
244 void eListboxStringContent::setList(std::list<std::string> &list)
245 {
246         m_list = list;
247         m_size = list.size();
248         cursorHome();
249 }
250
251 //////////////////////////////////////
252
253 DEFINE_REF(eListboxPythonStringContent);
254
255 eListboxPythonStringContent::eListboxPythonStringContent()
256 {
257         m_list = 0;
258 }
259
260 eListboxPythonStringContent::~eListboxPythonStringContent()
261 {
262 }
263
264 void eListboxPythonStringContent::cursorHome()
265 {
266         m_cursor = 0;
267 }
268
269 void eListboxPythonStringContent::cursorEnd()
270 {
271         m_cursor = size();
272 }
273
274 int eListboxPythonStringContent::cursorMove(int count)
275 {
276         m_cursor += count;
277         
278         if (m_cursor < 0)
279                 cursorHome();
280         else if (m_cursor > size())
281                 cursorEnd();
282         return 0;
283 }
284
285 int eListboxPythonStringContent::cursorValid()
286 {
287         return m_cursor < size();
288 }
289
290 int eListboxPythonStringContent::cursorSet(int n)
291 {
292         m_cursor = n;
293         
294         if (m_cursor < 0)
295                 cursorHome();
296         else if (m_cursor > size())
297                 cursorEnd();
298         return 0;
299 }
300
301 int eListboxPythonStringContent::cursorGet()
302 {
303         return m_cursor;
304 }
305
306 void eListboxPythonStringContent::cursorSave()
307 {
308         m_saved_cursor = m_cursor;
309 }
310
311 void eListboxPythonStringContent::cursorRestore()
312 {
313         m_cursor = m_saved_cursor;
314 }
315
316 int eListboxPythonStringContent::size()
317 {
318         if (!m_list)
319                 return 0;
320         return PyList_Size(m_list);
321 }
322         
323 void eListboxPythonStringContent::setSize(const eSize &size)
324 {
325         m_itemsize = size;
326 }
327
328 void eListboxPythonStringContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
329 {
330         ePtr<gFont> fnt = new gFont("Arial", 20);
331         painter.clip(eRect(offset, m_itemsize));
332         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
333         painter.clear();
334
335         if (m_list && cursorValid())
336         {
337                 PyObject *item = PyList_GetItem(m_list, m_cursor); // borrowed reference!
338                 painter.setFont(fnt);
339
340                         /* the user can supply tuples, in this case the first one will be displayed. */         
341                 if (PyTuple_Check(item))
342                         item = PyTuple_GetItem(item, 0);
343                 
344                 const char *string = PyString_Check(item) ? PyString_AsString(item) : "<not-a-string>";
345                 
346                 ePoint text_offset = offset + (selected ? ePoint(2, 2) : ePoint(1, 1));
347                 
348                 painter.renderText(eRect(text_offset, m_itemsize), string);
349                 
350                 if (selected)
351                         style.drawFrame(painter, eRect(offset, m_itemsize), eWindowStyle::frameListboxEntry);
352         }
353         
354         painter.clippop();
355 }
356
357 void eListboxPythonStringContent::setList(PyObject *list)
358 {
359         Py_XDECREF(m_list);
360         if (!PyList_Check(list))
361         {
362                 m_list = 0;
363         } else
364         {
365                 m_list = list;
366                 Py_INCREF(m_list);
367         }
368 }
369
370 PyObject *eListboxPythonStringContent::getCurrentSelection()
371 {
372         if (!m_list)
373                 return 0;
374         if (!cursorValid())
375                 return 0;
376         PyObject *r = PyList_GetItem(m_list, m_cursor);
377         Py_XINCREF(r);
378         return r;
379 }
380
381 void eListboxPythonStringContent::invalidateEntry(int index)
382 {
383         if (m_listbox)
384                 m_listbox->entryChanged(index);
385 }
386
387 void eListboxPythonStringContent::invalidate()
388 {
389         if (m_listbox)
390                 m_listbox->entryReset();
391 }
392
393 //////////////////////////////////////
394
395 void eListboxPythonConfigContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
396 {
397         ePtr<gFont> fnt = new gFont("Arial", 20);
398         ePtr<gFont> fnt2 = new gFont("Arial", 16);
399         painter.clip(eRect(offset, m_itemsize));
400         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
401         painter.clear();
402
403         if (m_list && cursorValid())
404         {
405                         /* get current list item */
406                 PyObject *item = PyList_GetItem(m_list, m_cursor); // borrowed reference!
407                 PyObject *text = 0, *value = 0;
408                 painter.setFont(fnt);
409
410                         /* the first tuple element is a string for the left side.
411                            the second one will be called, and the result shall be an tuple.
412                            
413                            of this tuple,
414                            the first one is the type (string).
415                            the second one is the value. */
416                 if (PyTuple_Check(item))
417                 {
418                                 /* handle left part. get item from tuple, convert to string, display. */
419                                 
420                         text = PyTuple_GetItem(item, 0);
421                         text = PyObject_Str(text);
422                         const char *string = (text && PyString_Check(text)) ? PyString_AsString(text) : "<not-a-string>";
423                         eSize item_left = eSize(m_seperation, m_itemsize.height());
424                         eSize item_right = eSize(m_itemsize.width() - m_seperation, m_itemsize.height());
425                         painter.renderText(eRect(offset, item_left), string, gPainter::RT_HALIGN_LEFT);
426                         Py_XDECREF(text);
427                         
428                                 /* now, handle the value. get 2nd part from tuple*/
429                         value = PyTuple_GetItem(item, 1);
430                         if (value)
431                         {
432                                 PyObject *args = PyTuple_New(1);
433                                 PyTuple_SetItem(args, 0, PyInt_FromLong(selected));
434                                 
435                                         /* CallObject will call __call__ which should return the value tuple */
436                                 value = PyObject_CallObject(value, args);
437                                 
438                                 if (PyErr_Occurred())
439                                         PyErr_Print();
440
441                                 Py_DECREF(args);
442                                         /* the PyInt was stolen. */
443                         }
444                         
445                                 /*  check if this is really a tuple */
446                         if (value && PyTuple_Check(value))
447                         {
448                                         /* convert type to string */
449                                 PyObject *type = PyTuple_GetItem(value, 0);
450                                 const char *atype = (type && PyString_Check(type)) ? PyString_AsString(type) : 0;
451                                 
452                                 if (atype)
453                                 {
454                                         if (!strcmp(atype, "text"))
455                                         {
456                                                 PyObject *pvalue = PyTuple_GetItem(value, 1);
457                                                 const char *value = (pvalue && PyString_Check(pvalue)) ? PyString_AsString(pvalue) : "<not-a-string>";
458                                                 painter.setFont(fnt2);
459                                                 painter.renderText(eRect(offset + eSize(m_seperation, 0), item_right), value, gPainter::RT_HALIGN_RIGHT);
460
461                                                         /* pvalue is borrowed */
462                                         } else if (!strcmp(atype, "slider"))
463                                         {
464                                                 PyObject *pvalue = PyTuple_GetItem(value, 1);
465                                                 
466                                                         /* convert value to Long. fallback to -1 on error. */
467                                                 int value = (pvalue && PyInt_Check(pvalue)) ? PyInt_AsLong(pvalue) : -1;
468                                                 
469                                                         /* calc. slider length */
470                                                 int width = item_right.width() * value / 100;
471                                                 int height = item_right.height();
472                                                 
473                                                                                                 
474                                                         /* draw slider */
475                                                 //painter.fill(eRect(offset.x() + m_seperation, offset.y(), width, height));
476                                                 //hack - make it customizable
477                                                 painter.fill(eRect(offset.x() + m_seperation, offset.y() + 5, width, height-10));
478                                                 
479                                                         /* pvalue is borrowed */
480                                         } else if (!strcmp(atype, "mtext"))
481                                         {
482                                                 PyObject *pvalue = PyTuple_GetItem(value, 1);
483                                                 const char *text = (pvalue && PyString_Check(pvalue)) ? PyString_AsString(pvalue) : "<not-a-string>";
484                                                 
485                                                 ePtr<eTextPara> para = new eTextPara(eRect(offset + eSize(m_seperation, 0), item_right));
486                                                 para->setFont(fnt2);
487                                                 para->renderString(text, 0);
488                                                 para->realign(eTextPara::dirRight);
489                                                 int glyphs = para->size();
490                                                 
491                                                 PyObject *plist = 0;
492                                                 
493                                                 if (PyTuple_Size(value) >= 3)
494                                                         plist = PyTuple_GetItem(value, 2);
495                                                 
496                                                 int entries = 0;
497
498                                                 if (plist && PyList_Check(plist))
499                                                         entries = PyList_Size(plist);
500                                                 
501                                                 for (int i = 0; i < entries; ++i)
502                                                 {
503                                                         PyObject *entry = PyList_GetItem(plist, i);
504                                                         int num = PyInt_Check(entry) ? PyInt_AsLong(entry) : -1;
505                                                         
506                                                         if ((num < 0) || (num >= glyphs))
507                                                                 eWarning("glyph index %d in PythonConfigList out of bounds!");
508                                                         else
509                                                         {
510                                                                 para->setGlyphFlag(num, GS_INVERT);
511                                                                 eRect bbox;
512                                                                 bbox = para->getGlyphBBox(num);
513                                                                 bbox = eRect(bbox.left(), offset.y(), bbox.width(), m_itemsize.height());
514                                                                 painter.fill(bbox);
515                                                         }
516                                                                 /* entry is borrowed */
517                                                 }
518                                                 
519                                                 painter.renderPara(para, ePoint(0, 0));
520                                                         /* pvalue is borrowed */
521                                                         /* plist is 0 or borrowed */
522                                         }
523                                 }
524                                 Py_XDECREF(type);
525                         } else
526                                 eWarning("eListboxPythonConfigContent: second value of tuple is not a tuple.");
527                                 /* value is borrowed */
528                 }
529
530                 if (selected)
531                         style.drawFrame(painter, eRect(offset, m_itemsize), eWindowStyle::frameListboxEntry);
532         }
533         
534         painter.clippop();
535 }
536
537 //////////////////////////////////////
538
539 void eListboxPythonMultiContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
540 {
541         painter.clip(eRect(offset, m_itemsize));
542         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
543         painter.clear();
544
545         if (m_list && cursorValid())
546         {
547                 PyObject *items = PyList_GetItem(m_list, m_cursor); // borrowed reference!
548                 
549                 if (!items)
550                 {
551                         eDebug("eListboxPythonMultiContent: error getting item %d", m_cursor);
552                         painter.clippop();
553                         return;
554                 }
555                 
556                 if (!PyList_Check(items))
557                 {
558                         eDebug("eListboxPythonMultiContent: list entry %d is not a list", m_cursor);
559                         painter.clippop();
560                         return;
561                 }
562                 
563                 int size = PyList_Size(items);
564                 for (int i = 1; i < size; ++i)
565                 {
566                         PyObject *item = PyList_GetItem(items, i); // borrowed reference!
567                         
568                         if (!item)
569                         {
570                                 eDebug("eListboxPythonMultiContent: ?");
571                                 painter.clippop();
572                                 return;
573                         }
574                         
575                         
576                         PyObject *px, *py, *pwidth, *pheight, *pfnt, *pstring, *pflags;
577                 
578                         /*
579                                 we have a list of tuples:
580                                 
581                                 (x, y, width, height, fnt, flags, "bla" ),
582                                 
583                          */
584                         
585                         if (!PyTuple_Check(item))
586                         {
587                                 eDebug("eListboxPythonMultiContent did not receive a tuple.");
588                                 painter.clippop();
589                                 return;
590                         }
591                 
592                         px = PyTuple_GetItem(item, 0);
593                         py = PyTuple_GetItem(item, 1);
594                         pwidth = PyTuple_GetItem(item, 2);
595                         pheight = PyTuple_GetItem(item, 3);
596                         pfnt = PyTuple_GetItem(item, 4);
597                         pflags = PyTuple_GetItem(item, 5);
598                         pstring = PyTuple_GetItem(item, 6);
599                         
600                         if (!(px && py && pwidth && pheight && pfnt && pstring))
601                         {
602                                 eDebug("eListboxPythonMultiContent received too small tuple (must be (x, y, width, height, fnt, flags, string[, ...])");
603                                 painter.clippop();
604                                 return;
605                         }
606         
607                         pstring = PyObject_Str(pstring);
608                         
609                         const char *string = (PyString_Check(pstring)) ? PyString_AsString(pstring) : "<not-a-string>";
610                         
611                         int x = PyInt_AsLong(px);
612                         int y = PyInt_AsLong(py);
613                         int width = PyInt_AsLong(pwidth);
614                         int height = PyInt_AsLong(pheight);
615                         int flags = PyInt_AsLong(pflags);
616                         
617                         int fnt = PyInt_AsLong(pfnt);
618                         
619                         if (m_font.find(fnt) == m_font.end())
620                         {
621                                 eDebug("eListboxPythonMultiContent: specified font %d was not found!", fnt);
622                                 Py_XDECREF(pstring);
623                                 painter.clippop();
624                                 return;
625                         }
626                         
627                         eRect r = eRect(x, y, width, height);
628                         r.moveBy(offset);
629                         
630                         painter.setFont(m_font[fnt]);
631                         
632                         painter.renderText(r, string, flags);
633         
634                         Py_XDECREF(pstring);
635                         
636                         if (selected)
637                                 style.drawFrame(painter, eRect(offset, m_itemsize), eWindowStyle::frameListboxEntry);
638                 }
639         }
640         
641         painter.clippop();
642 }
643
644 void eListboxPythonMultiContent::setFont(int fnt, gFont *font)
645 {
646         if (font)
647                 m_font[fnt] = font;
648         else
649                 m_font.erase(fnt);
650 }