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