1c797cfa24a285ae06325fe46a02b6ea59e832f4
[enigma2.git] / lib / gui / elistbox.cpp
1 #include <lib/gui/elistbox.h>
2 #include <lib/gui/elistboxcontent.h>
3 #include <lib/gui/eslider.h>
4 #include <lib/actions/action.h>
5
6 eListbox::eListbox(eWidget *parent)
7         :eWidget(parent), m_prev_scrollbar_page(-1), m_content_changed(false), m_scrollbar(NULL)
8 {
9         setContent(new eListboxStringContent());
10
11         ePtr<eActionMap> ptr;
12         eActionMap::getInstance(ptr);
13         
14         m_itemheight = 25;
15         m_selection_enabled = 1;
16         
17         ptr->bindAction("ListboxActions", 0, 0, this);
18 }
19
20 eListbox::~eListbox()
21 {
22         ePtr<eActionMap> ptr;
23         eActionMap::getInstance(ptr);
24         ptr->unbindAction(this, 0);
25 }
26
27 void eListbox::setScrollbarMode(int mode)
28 {
29         m_scrollbar_mode = mode;
30         if ( m_scrollbar_mode == showNever && m_scrollbar )
31         {
32                 delete m_scrollbar;
33                 m_scrollbar=0;
34         }
35         else if (!m_scrollbar)
36         {
37                 m_scrollbar = new eSlider(this);
38                 m_scrollbar->hide();
39                 m_scrollbar->setBorderWidth(1);
40                 m_scrollbar->setOrientation(eSlider::orVertical);
41                 m_scrollbar->setRange(0,100);
42         }
43 }
44
45 void eListbox::setContent(iListboxContent *content)
46 {
47         m_content = content;
48         if (content)
49                 m_content->setListbox(this);
50         entryReset();
51 }
52
53 void eListbox::moveSelection(int dir)
54 {
55                 /* refuse to do anything without a valid list. */
56         if (!m_content)
57                 return;
58                 
59                 /* we need the old top/sel to see what we have to redraw */
60         int oldtop = m_top;
61         int oldsel = m_selected;
62         
63                 /* first, move cursor */
64         switch (dir)
65         {
66         case moveUp:
67                 m_content->cursorMove(-1);
68                 break;
69         case moveDown:
70                 m_content->cursorMove(1);
71                         /* ok - we could have reached the end. we just go one back then. */
72                 if (!m_content->cursorValid())
73                         m_content->cursorMove(-1);
74                 break;
75         case pageUp:
76                 if (m_content->cursorGet() >= m_items_per_page)
77                 {
78                         m_content->cursorMove(-m_items_per_page);
79                         m_top -= m_items_per_page;
80                         if (m_top < 0)
81                                 m_top = 0;
82                 } else
83                 {
84                         m_top = 0;
85                         m_content->cursorHome();
86                 }
87                 break;
88         case moveTop:
89                 m_content->cursorHome();
90                 m_top = 0; /* align with top, speeds up process */
91                 break;
92
93         case pageDown:
94                 m_content->cursorMove(m_items_per_page);
95                 if (m_content->cursorValid())
96                         break;
97                                 /* fall through */
98         case moveEnd:
99                         /* move to last existing one ("end" is already invalid) */
100                 m_content->cursorEnd(); m_content->cursorMove(-1); 
101                         /* current selection invisible? */
102                 if (m_top + m_items_per_page <= m_content->cursorGet())
103                 {
104                         m_top = m_content->cursorGet() - m_items_per_page + 1;
105                         if (m_top < 0)
106                                 m_top = 0;
107                 }
108                 break;
109         case justCheck:
110                 break;
111         }
112         
113                 /* note that we could be on an invalid cursor position, but we don't
114                    care. this only happens on empty lists, and should have almost no
115                    side effects. */
116         
117                 /* now, look wether the current selection is out of screen */
118         m_selected = m_content->cursorGet();
119
120         while (m_selected < m_top)
121         {
122                 m_top -= m_items_per_page;
123                 if (m_top < 0)
124                         m_top = 0;
125         }
126         while (m_selected >= m_top + m_items_per_page)
127                 /* m_top should be always valid here as it's selected */
128                 m_top += m_items_per_page;
129
130         if (m_top != oldtop)
131                 invalidate();
132         else if (m_selected != oldsel)
133         {
134                 
135                         /* redraw the old and newly selected */
136                 gRegion inv = eRect(0, m_itemheight * (m_selected-m_top), size().width(), m_itemheight);
137                 inv |= eRect(0, m_itemheight * (oldsel-m_top), size().width(), m_itemheight);
138                 
139                 invalidate(inv);
140         }
141 }
142
143 void eListbox::moveSelectionTo(int index)
144 {
145         m_content->cursorHome();
146         m_content->cursorMove(index);
147         moveSelection(justCheck);
148 }
149
150 void eListbox::updateScrollBar()
151 {
152         if (!m_scrollbar)
153                 return;
154         int entries = m_content->size();
155         if ( m_content_changed )
156         {
157                 int width = size().width();
158                 int height = size().height();
159                 m_content_changed = false;
160                 if ( entries > m_items_per_page || m_scrollbar_mode == showAlways )
161                 {
162                         int sbarwidth=width/16;
163                         if ( sbarwidth < 18 )
164                                 sbarwidth=18;
165                         if ( sbarwidth > 22 )
166                                 sbarwidth=22;
167                         m_scrollbar->move(ePoint(width-sbarwidth, 0));
168                         m_scrollbar->resize(eSize(sbarwidth, height));
169                         m_content->setSize(eSize(width-sbarwidth-5, m_itemheight));
170                         if ( !m_scrollbar->isVisible() )
171                                 m_scrollbar->show();
172                 }
173                 else if ( m_scrollbar_mode != showAlways )
174                 {
175                         if ( m_scrollbar->isVisible() )
176                         {
177                                 m_content->setSize(eSize(width, m_itemheight));
178                                 m_scrollbar->hide(); // why this hide dont work???
179                         }
180                 }
181         }
182         int curVisiblePage = m_top / m_items_per_page;
183         if ( m_scrollbar->isVisible() &&
184                 m_prev_scrollbar_page != curVisiblePage)
185         {
186                 m_prev_scrollbar_page = curVisiblePage;
187                 int pages = entries / m_items_per_page;
188                 if ( (pages*m_items_per_page) < entries )
189                         ++pages;
190                 int start=(m_top*100)/(pages*m_items_per_page);
191                 int vis=(m_items_per_page*100)/(pages*m_items_per_page);
192                 if (vis < 3)
193                         vis=3;
194                 m_scrollbar->setStartEnd(start,start+vis);
195         }
196 }
197
198 int eListbox::event(int event, void *data, void *data2)
199 {
200         switch (event)
201         {
202         case evtPaint:
203         {
204                 ePtr<eWindowStyle> style;
205                 
206                 if (!m_content)
207                         return eWidget::event(event, data, data2);
208                 assert(m_content);
209                 
210                 getStyle(style);
211                 
212                 if (!m_content)
213                         return 0;
214                 
215                 gPainter &painter = *(gPainter*)data2;
216                 
217                 if (m_scrollbar_mode != showNever)
218                         updateScrollBar();
219                 
220                 m_content->cursorSave();
221                 m_content->cursorMove(m_top - m_selected);
222                 
223                 for (int y = 0, i = 0; i <= m_items_per_page; y += m_itemheight, ++i)
224                 {
225                         m_content->paint(painter, *style, ePoint(0, y), m_selected == m_content->cursorGet() && m_content->size() && m_selection_enabled);
226                         m_content->cursorMove(+1);
227                 }
228
229                 if ( m_scrollbar && m_scrollbar->isVisible() )
230                 {
231                         painter.clip(eRect(m_scrollbar->position() - ePoint(5,0), eSize(5,m_scrollbar->size().height())));
232                         painter.clear();
233                         painter.clippop();
234                 }
235
236                 m_content->cursorRestore();
237
238                 return 0;
239         }
240         case evtChangedSize:
241                 recalcSize();
242                 return eWidget::event(event, data, data2);
243                 
244         case evtAction:
245                 if (isVisible())
246                 {
247                         moveSelection((int)data2);
248                         return 1;
249                 }
250                 return 0;
251         default:
252                 return eWidget::event(event, data, data2);
253         }
254 }
255
256 void eListbox::recalcSize()
257 {
258         m_content_changed=true;
259         m_content->setSize(eSize(size().width(), m_itemheight));
260         m_items_per_page = size().height() / m_itemheight;
261 }
262
263 void eListbox::setItemHeight(int h)
264 {
265         if (h)
266                 m_itemheight = h;
267         else
268                 m_itemheight = 20;
269         recalcSize();
270 }
271
272 void eListbox::setSelectionEnable(int en)
273 {
274         if (m_selection_enabled == en)
275                 return;
276         m_selection_enabled = en;
277         entryChanged(m_selected); /* redraw current entry */
278 }
279
280 void eListbox::entryAdded(int index)
281 {
282                 /* manage our local pointers. when the entry was added before the current position, we have to advance. */
283                 
284                 /* we need to check <= - when the new entry has the (old) index of the cursor, the cursor was just moved down. */
285         if (index <= m_selected)
286                 ++m_selected;
287         if (index <= m_top)
288                 ++m_top;
289                 
290                 /* we have to check wether our current cursor is gone out of the screen. */
291                 /* moveSelection will check for this case */
292         moveSelection(justCheck);
293         
294                 /* now, check if the new index is visible. */
295         if ((m_top <= index) && (index < (m_top + m_items_per_page)))
296         {
297                         /* todo, calc exact invalidation... */
298                 invalidate();
299         }
300 }
301
302 void eListbox::entryRemoved(int index)
303 {
304         if (index == m_selected)
305                 m_selected = m_content->cursorGet();
306
307         moveSelection(justCheck);
308         
309         if ((m_top <= index) && (index < (m_top + m_items_per_page)))
310         {
311                         /* todo, calc exact invalidation... */
312                 invalidate();
313         }
314 }
315
316 void eListbox::entryChanged(int index)
317 {
318         if ((m_top <= index) && (index < (m_top + m_items_per_page)))
319         {
320                 gRegion inv = eRect(0, m_itemheight * (index-m_top), size().width(), m_itemheight);
321                 invalidate(inv);
322         }
323 }
324
325 void eListbox::entryReset()
326 {
327         m_content_changed=true;
328         m_prev_scrollbar_page=-1;
329         if (m_content)
330                 m_content->cursorHome();
331         m_top = 0;
332         m_selected = 0;
333         invalidate();
334 }