add scrollbar to listbox
[vuplus_dvbapp] / 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         setScrollbarMode(showOnDemand);   // Default show scrollbar on demand
12
13         ePtr<eActionMap> ptr;
14         eActionMap::getInstance(ptr);
15         
16         m_itemheight = 25;
17         m_selection_enabled = 1;
18         
19         ptr->bindAction("ListboxActions", 0, 0, this);
20 }
21
22 eListbox::~eListbox()
23 {
24         ePtr<eActionMap> ptr;
25         eActionMap::getInstance(ptr);
26         ptr->unbindAction(this, 0);
27 }
28
29 void eListbox::setScrollbarMode(int mode)
30 {
31         m_scrollbar_mode = mode;
32         if ( m_scrollbar_mode == showNever && m_scrollbar )
33         {
34                 delete m_scrollbar;
35                 m_scrollbar=0;
36         }
37         else if (!m_scrollbar)
38         {
39                 m_scrollbar = new eSlider(this);
40                 m_scrollbar->hide();
41                 m_scrollbar->setBorderWidth(1);
42                 m_scrollbar->setOrientation(eSlider::orVertical);
43                 m_scrollbar->setRange(0,100);
44         }
45 }
46
47 void eListbox::setContent(iListboxContent *content)
48 {
49         m_content = content;
50         if (content)
51                 m_content->setListbox(this);
52         entryReset();
53 }
54
55 void eListbox::moveSelection(int dir)
56 {
57                 /* refuse to do anything without a valid list. */
58         if (!m_content)
59                 return;
60                 
61                 /* we need the old top/sel to see what we have to redraw */
62         int oldtop = m_top;
63         int oldsel = m_selected;
64         
65                 /* first, move cursor */
66         switch (dir)
67         {
68         case moveUp:
69                 m_content->cursorMove(-1);
70                 break;
71         case moveDown:
72                 m_content->cursorMove(1);
73                         /* ok - we could have reached the end. we just go one back then. */
74                 if (!m_content->cursorValid())
75                         m_content->cursorMove(-1);
76                 break;
77         case pageUp:
78                 if (m_content->cursorGet() >= m_items_per_page)
79                 {
80                         m_content->cursorMove(-m_items_per_page);
81                         m_top -= m_items_per_page;
82                         if (m_top < 0)
83                                 m_top = 0;
84                 } else
85                 {
86                         m_top = 0;
87                         m_content->cursorHome();
88                 }
89                 break;
90         case moveTop:
91                 m_content->cursorHome();
92                 m_top = 0; /* align with top, speeds up process */
93                 break;
94
95         case pageDown:
96                 m_content->cursorMove(m_items_per_page);
97                 if (m_content->cursorValid())
98                         break;
99                                 /* fall through */
100         case moveEnd:
101                         /* move to last existing one ("end" is already invalid) */
102                 m_content->cursorEnd(); m_content->cursorMove(-1); 
103                         /* current selection invisible? */
104                 if (m_top + m_items_per_page <= m_content->cursorGet())
105                 {
106                         m_top = m_content->cursorGet() - m_items_per_page + 1;
107                         if (m_top < 0)
108                                 m_top = 0;
109                 }
110                 break;
111         case justCheck:
112                 break;
113         }
114         
115                 /* note that we could be on an invalid cursor position, but we don't
116                    care. this only happens on empty lists, and should have almost no
117                    side effects. */
118         
119                 /* now, look wether the current selection is out of screen */
120         m_selected = m_content->cursorGet();
121
122         while (m_selected < m_top)
123         {
124                 m_top -= m_items_per_page;
125                 if (m_top < 0)
126                         m_top = 0;
127         }
128         while (m_selected >= m_top + m_items_per_page)
129                 /* m_top should be always valid here as it's selected */
130                 m_top += m_items_per_page;
131
132         if (m_top != oldtop)
133                 invalidate();
134         else if (m_selected != oldsel)
135         {
136                 
137                         /* redraw the old and newly selected */
138                 gRegion inv = eRect(0, m_itemheight * (m_selected-m_top), size().width(), m_itemheight);
139                 inv |= eRect(0, m_itemheight * (oldsel-m_top), size().width(), m_itemheight);
140                 
141                 invalidate(inv);
142         }
143 }
144
145 void eListbox::moveSelectionTo(int index)
146 {
147         m_content->cursorHome();
148         m_content->cursorMove(index);
149         moveSelection(justCheck);
150 }
151
152 void eListbox::updateScrollBar()
153 {
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 }