- add listbox
[vuplus_dvbapp] / lib / gui / elistbox.cpp
1 #include <lib/gui/elistbox.h>
2
3 /*
4     The basic idea is to have an interface which gives all relevant list
5     processing functions, and can be used by the listbox to browse trough
6     the list.
7     
8     The listbox directly uses the implemented cursor. It tries hard to avoid
9     iterating trough the (possibly very large) list, so it should be O(1),
10     i.e. the performance should not be influenced by the size of the list.
11     
12     The list interface knows how to draw the current entry to a specified 
13     offset. Different interfaces can be used to adapt different lists,
14     pre-filter lists on the fly etc.
15     
16                 cursorSave/Restore is used to avoid re-iterating the list on redraw.
17                 The current selection is always selected as cursor position, the
18     cursor is then positioned to the start, and then iterated. This gives
19     at most 2x m_items_per_page cursor movements per redraw, indepenent
20     of the size of the list.
21     
22     Although cursorSet is provided, it should be only used when there is no
23     other way, as it involves iterating trough the list.
24  */
25
26 class eListboxTestContent: public virtual iListboxContent
27 {
28         DECLARE_REF;
29 public:
30         void cursorHome();
31         void cursorEnd();
32         int cursorMove(int count=1);
33         int cursorValid();
34         int cursorSet(int n);
35         int cursorGet();
36         
37         void cursorSave();
38         void cursorRestore();
39         int size();
40         
41         RESULT connectItemChanged(const Slot0<void> &itemChanged, ePtr<eConnection> &connection);
42         
43         // void setOutputDevice ? (for allocating colors, ...) .. requires some work, though
44         void setSize(const eSize &size);
45         
46                 /* the following functions always refer to the selected item */
47         void paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected);
48 private:
49         int m_cursor, m_saved_cursor;
50         eSize m_size;
51 };
52
53 DEFINE_REF(eListboxTestContent);
54
55 void eListboxTestContent::cursorHome()
56 {
57         m_cursor = 0;
58 }
59
60 void eListboxTestContent::cursorEnd()
61 {
62         m_cursor = size();
63 }
64
65 int eListboxTestContent::cursorMove(int count)
66 {
67         m_cursor += count;
68         
69         if (m_cursor < 0)
70                 cursorHome();
71         else if (m_cursor > size())
72                 cursorEnd();
73         return 0;
74 }
75
76 int eListboxTestContent::cursorValid()
77 {
78         return m_cursor < size();
79 }
80
81 int eListboxTestContent::cursorSet(int n)
82 {
83         m_cursor = n;
84         
85         if (m_cursor < 0)
86                 cursorHome();
87         else if (m_cursor > size())
88                 cursorEnd();
89         return 0;
90 }
91
92 int eListboxTestContent::cursorGet()
93 {
94         return m_cursor;
95 }
96
97 void eListboxTestContent::cursorSave()
98 {
99         m_saved_cursor = m_cursor;
100 }
101
102 void eListboxTestContent::cursorRestore()
103 {
104         m_cursor = m_saved_cursor;
105 }
106
107 int eListboxTestContent::size()
108 {
109         return 10;
110 }
111         
112 RESULT eListboxTestContent::connectItemChanged(const Slot0<void> &itemChanged, ePtr<eConnection> &connection)
113 {
114         return 0;
115 }
116
117 void eListboxTestContent::setSize(const eSize &size)
118 {
119         m_size = size;
120 }
121
122 void eListboxTestContent::paint(gPainter &painter, eWindowStyle &style, const ePoint &offset, int selected)
123 {
124         ePtr<gFont> fnt = new gFont("Arial", 14);
125         painter.clip(eRect(offset, m_size));
126         style.setStyle(painter, selected ? eWindowStyle::styleListboxSelected : eWindowStyle::styleListboxNormal);
127         painter.clear();
128
129         if (cursorValid())
130         {
131                 painter.setFont(fnt);
132                 char string[10];
133                 sprintf(string, "%d.)", m_cursor);
134                 
135                 ePoint text_offset = offset + (selected ? ePoint(2, 2) : ePoint(1, 1));
136                 
137                 painter.renderText(eRect(text_offset, m_size), string);
138                 
139                 if (selected)
140                         style.drawFrame(painter, eRect(offset, m_size), eWindowStyle::frameListboxEntry);
141         }
142         
143         painter.clippop();
144 }
145
146 eListbox::eListbox(eWidget *parent): eWidget(parent)
147 {
148         setContent(new eListboxTestContent());
149         m_content->cursorHome();
150         m_top = 0;
151         m_selected = 0;
152 }
153
154 void eListbox::setContent(iListboxContent *content)
155 {
156         m_content = content;
157 }
158
159 void eListbox::moveSelection(int dir)
160 {
161                 /* we need the old top/sel to see what we have to redraw */
162         int oldtop = m_top;
163         int oldsel = m_selected;
164         
165                 /* first, move cursor */
166         switch (dir)
167         {
168         case moveUp:
169                 m_content->cursorMove(-1);
170                 break;
171         case moveDown:
172                 m_content->cursorMove(1);
173                         /* ok - we could have reached the end. we just go one back then. */
174                 if (!m_content->cursorValid())
175                         m_content->cursorMove(-1);
176                 break;
177         case moveTop:
178                 m_content->cursorHome();
179                 m_top = 0; /* align with top, speeds up process */
180                 break;
181         case moveEnd:
182                         /* move to last existing one ("end" is already invalid) */
183                 m_content->cursorEnd(); m_content->cursorMove(-1); 
184                 
185                 m_top = m_content->cursorGet() - m_items_per_page + 1;
186                 if (m_top < 0)
187                         m_top = 0;
188                 break;
189         }
190         
191                 /* note that we could be on an invalid cursor position, but we don't
192                    care. this only happens on empty lists, and should have almost no
193                    side effects. */
194         
195                 /* now, look wether the current selection is out of screen */
196         m_selected = m_content->cursorGet();
197         if (m_selected < m_top)
198         {
199                 m_top -= m_items_per_page;
200                 if (m_top < 0)
201                         m_top = 0;
202         } else if (m_selected >= m_top + m_items_per_page)
203         {
204                         /* m_top should be always valid here as it's selected */
205                 m_top += m_items_per_page;
206         }
207         
208         if (m_top != oldtop)
209                 invalidate();
210         else
211         {
212                         /* redraw the old and newly selected */
213                 gRegion inv = eRect(0, m_itemheight * (m_selected-m_top), size().width(), m_itemheight);
214                 inv |= eRect(0, m_itemheight * (oldsel-m_top), size().width(), m_itemheight);
215                 
216                 invalidate(inv);
217         }
218 }
219
220 int eListbox::event(int event, void *data, void *data2)
221 {
222         switch (event)
223         {
224         case evtPaint:
225         {
226                 ePtr<eWindowStyle> style;
227                 
228                 assert(m_content);
229                 recalcSize(); // move to event
230                 
231                 getStyle(style);
232                 
233                 if (!m_content)
234                         return 0;
235                 
236                 gPainter &painter = *(gPainter*)data2;
237                 
238                 m_content->cursorSave();
239                 m_content->cursorMove(m_top - m_selected);
240                 
241                 for (int y = 0, i = 0; i < m_items_per_page; y += m_itemheight, ++i)
242                 {
243                         m_content->paint(painter, *style, ePoint(0, y), m_selected == m_content->cursorGet());
244                         m_content->cursorMove(+1);
245                 }
246                 
247                 m_content->cursorRestore();
248                 
249                 return 0;
250         }
251         default:
252                 return eWidget::event(event, data, data2);
253         }
254 }
255
256 void eListbox::recalcSize()
257 {
258         m_itemheight = 20;
259         m_content->setSize(eSize(size().width(), m_itemheight));
260         m_items_per_page = size().height() / m_itemheight;
261 }