1 /*
2  * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3  *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4  *
5  * This file is part of lsp-plugins
6  * Created on: 2 авг. 2017 г.
7  *
8  * lsp-plugins is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * lsp-plugins is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <ui/tk/tk.h>
23 
24 namespace lsp
25 {
26     namespace tk
27     {
28         const w_class_t LSPListBox::metadata = { "LSPListBox", &LSPComplexWidget::metadata };
29 
30         //-----------------------------------------------------------------------------
31         // LSPListBoxList implementation
LSPListBoxList(LSPListBox * widget)32         LSPListBox::LSPListBoxList::LSPListBoxList(LSPListBox *widget)
33         {
34             pWidget     = widget;
35         }
36 
~LSPListBoxList()37         LSPListBox::LSPListBoxList::~LSPListBoxList()
38         {
39             pWidget     = NULL;
40         }
41 
on_item_change(LSPListItem * item)42         void LSPListBox::LSPListBoxList::on_item_change(LSPListItem *item)
43         {
44             ssize_t index = pWidget->items()->index_of(item);
45             if (index >= 0)
46                 pWidget->on_item_change(index, item);
47         }
48 
on_item_add(size_t index)49         void LSPListBox::LSPListBoxList::on_item_add(size_t index)
50         {
51             pWidget->on_item_add(index);
52         }
53 
on_item_remove(size_t index)54         void LSPListBox::LSPListBoxList::on_item_remove(size_t index)
55         {
56             pWidget->on_item_remove(index);
57         }
58 
on_item_swap(size_t idx1,size_t idx2)59         void LSPListBox::LSPListBoxList::on_item_swap(size_t idx1, size_t idx2)
60         {
61             pWidget->on_item_swap(idx1, idx2);
62         }
63 
on_item_clear()64         void LSPListBox::LSPListBoxList::on_item_clear()
65         {
66             pWidget->on_item_clear();
67         }
68 
69         //-----------------------------------------------------------------------------
70         // LSPListBoxSelection implementation
71 
LSPListBoxSelection(LSPListBox * widget)72         LSPListBox::LSPListBoxSelection::LSPListBoxSelection(LSPListBox *widget)
73         {
74             pWidget     = widget;
75         }
76 
~LSPListBoxSelection()77         LSPListBox::LSPListBoxSelection::~LSPListBoxSelection()
78         {
79             pWidget     = NULL;
80         }
81 
on_remove(ssize_t value)82         void LSPListBox::LSPListBoxSelection::on_remove(ssize_t value)
83         {
84             float fh      = pWidget->sFont.height();
85             ssize_t first = pWidget->sVBar.value() / fh;
86             ssize_t last  = (pWidget->sVBar.value() + pWidget->sArea.nHeight + fh - 1) / fh;
87 
88             if ((value >= first) || (value <= last))
89                 pWidget->query_draw();
90 
91             pWidget->on_selection_change();
92         }
93 
on_add(ssize_t value)94         void LSPListBox::LSPListBoxSelection::on_add(ssize_t value)
95         {
96             float fh      = pWidget->sFont.height();
97             ssize_t first = pWidget->sVBar.value() / fh;
98             ssize_t last  = (pWidget->sVBar.value() + pWidget->sArea.nHeight + fh) / fh;
99 
100             if ((value >= first) || (value <= last))
101                 pWidget->query_draw();
102 
103             pWidget->on_selection_change();
104         }
105 
validate(ssize_t value)106         bool LSPListBox::LSPListBoxSelection::validate(ssize_t value)
107         {
108             if (pWidget == NULL)
109                 return false;
110             return (value >= 0) && (value < ssize_t(pWidget->sItems.size()));
111         }
112 
request_fill(ssize_t * first,ssize_t * last)113         void LSPListBox::LSPListBoxSelection::request_fill(ssize_t *first, ssize_t *last)
114         {
115             *first  = 0;
116             *last   = (pWidget != NULL) ? pWidget->sItems.size() - 1 : -1;
117         }
118 
on_fill()119         void LSPListBox::LSPListBoxSelection::on_fill()
120         {
121             pWidget->query_draw();
122             pWidget->on_selection_change();
123         }
124 
on_clear()125         void LSPListBox::LSPListBoxSelection::on_clear()
126         {
127             pWidget->query_draw();
128             pWidget->on_selection_change();
129         }
130 
131         //-----------------------------------------------------------------------------
132         // LSPListBox implementation
LSPListBox(LSPDisplay * dpy)133         LSPListBox::LSPListBox(LSPDisplay *dpy):
134             LSPComplexWidget(dpy),
135             sItems(this),
136             sSelection(this),
137             sHBar(dpy, true),
138             sVBar(dpy, false),
139             sConstraints(this),
140             sColor(this),
141             sFont(this)
142         {
143             nFlags              = 0;
144             nBMask              = 0;
145             pArea               = NULL;
146 
147             pClass              = &metadata;
148         }
149 
~LSPListBox()150         LSPListBox::~LSPListBox()
151         {
152             do_destroy();
153         }
154 
hide()155         bool LSPListBox::hide()
156         {
157             bool result = LSPComplexWidget::hide();
158 
159             if (result)
160             {
161                 // Drop area to not to eat memory
162                 if (pArea != NULL)
163                 {
164                     pArea->destroy();
165                     delete pArea;
166                     pArea   = NULL;
167                 }
168             }
169             return result;
170         }
171 
do_destroy()172         void LSPListBox::do_destroy()
173         {
174             // Clear contents
175             sItems.clear();
176             sSelection.clear();
177             sHBar.destroy();
178             sVBar.destroy();
179 
180             // Drop area to not to eat memory
181             if (pArea != NULL)
182             {
183                 pArea->destroy();
184                 delete pArea;
185                 pArea   = NULL;
186             }
187         }
188 
init()189         status_t LSPListBox::init()
190         {
191             status_t result = LSPWidget::init();
192             if (result != STATUS_OK)
193                 return result;
194 
195             init_color(C_LABEL_TEXT, &sColor);
196             init_color(C_LABEL_TEXT, sFont.color());
197 
198             result = sHBar.init();
199             if (result != STATUS_OK)
200                 return result;
201             result = sVBar.init();
202             if (result != STATUS_OK)
203                 return result;
204 
205             sVBar.set_parent(this);
206             sHBar.set_parent(this);
207             sVBar.hide();
208             sHBar.hide();
209 
210             sFont.init();
211             sFont.set_size(12);
212 
213             // Bind slots
214             ui_handler_id_t id = 0;
215             id = sSlots.add(LSPSLOT_CHANGE, slot_on_change, self());
216             if (id >= 0) id = sSlots.add(LSPSLOT_SUBMIT, slot_on_submit, self());
217             if (id >= 0) id = sSlots.add(LSPSLOT_HSCROLL, slot_on_hscroll, self());
218             if (id >= 0) id = sSlots.add(LSPSLOT_VSCROLL, slot_on_vscroll, self());
219             if (id >= 0) id = sVBar.slots()->bind(LSPSLOT_CHANGE, slot_on_sbar_vscroll, self());
220             if (id >= 0) id = sHBar.slots()->bind(LSPSLOT_CHANGE, slot_on_sbar_hscroll, self());
221 
222             return (id >= 0) ? STATUS_OK : -id;
223         }
224 
on_item_change(size_t index,LSPItem * item)225         void LSPListBox::on_item_change(size_t index, LSPItem *item)
226         {
227             float fh      = sFont.height();
228             ssize_t first = sVBar.value() / fh;
229             ssize_t last  = (sVBar.value() + sArea.nHeight + fh - 1) / fh;
230 
231             if ((ssize_t(index) >= first) || (ssize_t(index) <= last))
232                 query_draw();
233         }
234 
on_item_add(size_t index)235         void LSPListBox::on_item_add(size_t index)
236         {
237             realize(&sSize);
238             query_resize();
239         }
240 
on_item_remove(size_t index)241         void LSPListBox::on_item_remove(size_t index)
242         {
243             realize(&sSize);
244             query_resize();
245         }
246 
on_item_swap(size_t idx1,size_t idx2)247         void LSPListBox::on_item_swap(size_t idx1, size_t idx2)
248         {
249             float fh      = sFont.height();
250             ssize_t first = sVBar.value() / fh;
251             ssize_t last  = (sVBar.value() + sArea.nHeight + fh - 1) / fh;
252 
253             if ((ssize_t(idx1) >= first) || (ssize_t(idx1) <= last) || (ssize_t(idx2) >= first) || (ssize_t(idx2) <= last))
254                 query_draw();
255         }
256 
on_item_clear()257         void LSPListBox::on_item_clear()
258         {
259             realize(&sSize);
260             query_resize();
261         }
262 
on_selection_change()263         void LSPListBox::on_selection_change()
264         {
265         }
266 
destroy()267         void LSPListBox::destroy()
268         {
269             do_destroy();
270             LSPWidget::destroy();
271         }
272 
slot_on_sbar_vscroll(LSPWidget * sender,void * ptr,void * data)273         status_t LSPListBox::slot_on_sbar_vscroll(LSPWidget *sender, void *ptr, void *data)
274         {
275             if (ptr == NULL)
276                 return STATUS_BAD_ARGUMENTS;
277 
278             LSPWidget *w    = static_cast<LSPWidget *>(ptr);
279             return w->slots()->execute(LSPSLOT_VSCROLL, sender, data);
280         }
281 
slot_on_sbar_hscroll(LSPWidget * sender,void * ptr,void * data)282         status_t LSPListBox::slot_on_sbar_hscroll(LSPWidget *sender, void *ptr, void *data)
283         {
284             if (ptr == NULL)
285                 return STATUS_BAD_ARGUMENTS;
286 
287             LSPWidget *w    = static_cast<LSPWidget *>(ptr);
288             return w->slots()->execute(LSPSLOT_HSCROLL, sender, data);
289         }
290 
slot_on_change(LSPWidget * sender,void * ptr,void * data)291         status_t LSPListBox::slot_on_change(LSPWidget *sender, void *ptr, void *data)
292         {
293             LSPListBox *_this = widget_ptrcast<LSPListBox>(ptr);
294             return (_this != NULL) ? _this->on_change() : STATUS_BAD_ARGUMENTS;
295         }
296 
slot_on_submit(LSPWidget * sender,void * ptr,void * data)297         status_t LSPListBox::slot_on_submit(LSPWidget *sender, void *ptr, void *data)
298         {
299             LSPListBox *_this = widget_ptrcast<LSPListBox>(ptr);
300             return (_this != NULL) ? _this->on_submit() : STATUS_BAD_ARGUMENTS;
301         }
302 
slot_on_vscroll(LSPWidget * sender,void * ptr,void * data)303         status_t LSPListBox::slot_on_vscroll(LSPWidget *sender, void *ptr, void *data)
304         {
305             LSPListBox *_this = widget_ptrcast<LSPListBox>(ptr);
306             return (_this != NULL) ? _this->on_vscroll() : STATUS_BAD_ARGUMENTS;
307         }
308 
slot_on_hscroll(LSPWidget * sender,void * ptr,void * data)309         status_t LSPListBox::slot_on_hscroll(LSPWidget *sender, void *ptr, void *data)
310         {
311             LSPListBox *_this = widget_ptrcast<LSPListBox>(ptr);
312             return (_this != NULL) ? _this->on_hscroll() : STATUS_BAD_ARGUMENTS;
313         }
314 
find_widget(ssize_t x,ssize_t y)315         LSPWidget *LSPListBox::find_widget(ssize_t x, ssize_t y)
316         {
317             if (sHBar.visible() && sHBar.inside(x, y))
318                 return &sHBar;
319             if (sVBar.visible() && sVBar.inside(x, y))
320                 return &sVBar;
321             return NULL;
322         }
323 
on_change()324         status_t LSPListBox::on_change()
325         {
326             return STATUS_OK;
327         }
328 
on_submit()329         status_t LSPListBox::on_submit()
330         {
331             return STATUS_OK;
332         }
333 
on_click(ssize_t x,ssize_t y)334         void LSPListBox::on_click(ssize_t x, ssize_t y)
335         {
336             lsp_trace("x=%d, y=%d, area={%d, %d, %d, %d}",
337                 int(x), int(y), int(sArea.nLeft), int(sArea.nTop), int(sArea.nLeft + sArea.nWidth), int (sArea.nTop + sArea.nHeight));
338             if ((x < sArea.nLeft) || (x >= (sArea.nLeft + sArea.nWidth)))
339                 return;
340             else if ((y < sArea.nTop) || (y >= (sArea.nTop + sArea.nHeight)))
341                 return;
342 
343             x       = x - sArea.nLeft;
344             y       = y - sArea.nTop + sVBar.value();
345 
346             float fh      = sFont.height();
347             ssize_t item    = y / fh;
348             lsp_trace("toggled item = %d", int(item));
349             if (sSelection.multiple())
350             {
351                 sSelection.toggle_value(item);
352                 sSlots.execute(LSPSLOT_CHANGE, this);
353             }
354             else
355             {
356                 ssize_t old_value = sSelection.value();
357                 sSelection.set_value(item);
358                 if (old_value != item)
359                     sSlots.execute(LSPSLOT_CHANGE, this);
360             }
361 
362             nFlags |= F_SUBMIT;
363         }
364 
on_mouse_down(const ws_event_t * e)365         status_t LSPListBox::on_mouse_down(const ws_event_t *e)
366         {
367             lsp_trace("x=%d, y=%d, code=%x, bmask=%lx", int(e->nLeft), int(e->nTop), int(e->nCode), long(nBMask));
368             take_focus();
369             size_t mask = nBMask;
370             nBMask      = mask | (1 << e->nCode);
371 
372             if ((mask == 0) && (e->nCode == MCB_LEFT))
373             {
374                 nFlags |= F_MDOWN;
375                 on_click(e->nLeft, e->nTop);
376             }
377 
378             return STATUS_OK;
379         }
380 
on_mouse_up(const ws_event_t * e)381         status_t LSPListBox::on_mouse_up(const ws_event_t *e)
382         {
383             lsp_trace("x=%d, y=%d, code=%x, bmask=%lx", int(e->nLeft), int(e->nTop), int(e->nCode), long(nBMask));
384             nBMask      = nBMask & (~(1 << e->nCode));
385             if (nBMask == 0)
386                 nFlags      &= ~F_MDOWN;
387 
388             if (nFlags & F_SUBMIT)
389             {
390                 nFlags      &= ~F_SUBMIT;
391                 sSlots.execute(LSPSLOT_SUBMIT, this);
392             }
393             return STATUS_OK;
394         }
395 
on_mouse_move(const ws_event_t * e)396         status_t LSPListBox::on_mouse_move(const ws_event_t *e)
397         {
398             if (sSelection.multiple())
399                 return STATUS_OK;
400 
401             if (nBMask == (1 << MCB_LEFT))
402                 on_click(e->nLeft, e->nTop);
403 
404             return STATUS_OK;
405         }
406 
on_mouse_scroll(const ws_event_t * e)407         status_t LSPListBox::on_mouse_scroll(const ws_event_t *e)
408         {
409             if (e->nState & MCF_CONTROL)
410             {
411                 ws_event_t xe = *e;
412                 xe.nState &= ~MCF_CONTROL;
413                 sHBar.handle_event(&xe);
414             }
415             else
416                 sVBar.handle_event(e);
417 
418             return STATUS_OK;
419         }
420 
on_hscroll()421         status_t LSPListBox::on_hscroll()
422         {
423             query_draw();
424             return STATUS_OK;
425         }
426 
on_vscroll()427         status_t LSPListBox::on_vscroll()
428         {
429             query_draw();
430             return STATUS_OK;
431         }
432 
render(ISurface * s,bool force)433         void LSPListBox::render(ISurface *s, bool force)
434         {
435             // Check dirty flag
436             if (nFlags & REDRAW_SURFACE)
437                 force = true;
438 
439             // Draw list box
440             ISurface *lst = get_surface(s, sArea.nWidth, sArea.nHeight);
441             if (lst != NULL)
442                 s->draw(lst, sArea.nLeft, sArea.nTop);
443 
444             // Prepare palette
445             Color bg_color(sBgColor);
446             Color color(sColor);
447             color.scale_lightness(brightness());
448 
449             // Draw the frame around
450             size_t dx = (sVBar.visible()) ? 7 : 6;
451             size_t dy = (sHBar.visible()) ? 7 : 6;
452 
453             s->fill_frame(sSize.nLeft, sSize.nTop, sArea.nWidth + dx, sArea.nHeight + dy,
454                         sArea.nLeft, sArea.nTop, sArea.nWidth, sArea.nHeight,
455                         bg_color);
456 
457             bool aa = s->set_antialiasing(true);
458             s->wire_round_rect(sSize.nLeft + 0.5f, sSize.nTop + 0.5f, sArea.nWidth + 5, sArea.nHeight + 5, 2, SURFMASK_ALL_CORNER, 1, color);
459             s->set_antialiasing(aa);
460 
461             // Finally, draw scroll bars
462             if (sHBar.visible())
463             {
464                 if ((sHBar.redraw_pending()) || (force))
465                 {
466                     sHBar.render(s, false);
467                     sHBar.commit_redraw();
468                 }
469             }
470             if (sVBar.visible())
471             {
472                 if ((sVBar.redraw_pending()) || (force))
473                 {
474                     sVBar.render(s, false);
475                     sVBar.commit_redraw();
476                 }
477             }
478         }
479 
draw(ISurface * s)480         void LSPListBox::draw(ISurface *s)
481         {
482             // Prepare palette
483             Color bg_color(sBgColor);
484             Color color(sColor);
485             Color font(sFont.raw_color());
486 
487             color.scale_lightness(brightness());
488             font.scale_lightness(brightness());
489 
490             // Draw background
491             s->clear(bg_color);
492 
493             // Draw
494             font_parameters_t fp;
495             sFont.get_parameters(s, &fp);
496 
497             ssize_t first = sVBar.value() / fp.Height;
498             ssize_t last  = (sVBar.value() + sArea.nHeight + fp.Height - 1) / fp.Height;
499             ssize_t y     = first * fp.Height - sVBar.value();
500 
501             LSPString text;
502             for ( ; first <= last; first++, y += fp.Height)
503             {
504                 LSPItem *item = sItems.get(first);
505                 if (item == NULL)
506                     continue;
507 
508                 item->text()->format(&text, this);
509                 if (sSelection.contains(first))
510                 {
511                     s->fill_rect(0.0f, y, sArea.nWidth, fp.Height, font);
512                     if (text.length() > 0)
513                         sFont.draw(s, 1.0f, y + fp.Ascent, bg_color, &text);
514                 }
515                 else if (text.length() > 0)
516                     sFont.draw(s, 1.0f, y + fp.Ascent, font, &text);
517             }
518         }
519 
size_request(size_request_t * r)520         void LSPListBox::size_request(size_request_t *r)
521         {
522             size_request_t hbar, vbar;
523             hbar.nMinWidth   = -1;
524             hbar.nMinHeight  = -1;
525             hbar.nMaxWidth   = -1;
526             hbar.nMaxHeight  = -1;
527             vbar.nMinWidth   = -1;
528             vbar.nMinHeight  = -1;
529             vbar.nMaxWidth   = -1;
530             vbar.nMaxHeight  = -1;
531 
532             sHBar.size_request(&hbar);
533             sVBar.size_request(&vbar);
534 
535             // Estimate minimum size of bars
536             ssize_t width = 0, height = 0;
537             if (hbar.nMinWidth >= 0)
538                 width       += hbar.nMinWidth;
539             if (vbar.nMinWidth >= 0)
540                 width       += vbar.nMinWidth;
541 
542             if (hbar.nMinHeight >= 0)
543                 height      += hbar.nMinHeight;
544             if (vbar.nMinHeight >= 0)
545                 height      += vbar.nMinHeight;
546 
547             size_t padding  = 6;
548             size_t n_items  = sItems.size();
549             if (n_items <= 0)
550                 n_items ++;
551             ssize_t i_height = sFont.height() * n_items + padding;
552             if (height > i_height)
553                 height = i_height;
554 
555             // Fill final values
556             r->nMinWidth    = width;
557             r->nMinHeight   = height;
558             r->nMaxWidth    = -1;
559             r->nMaxHeight   = -1;
560 
561             // Apply constraints
562             sConstraints.apply(r);
563         }
564 
optimal_size_request(size_request_t * r)565         void LSPListBox::optimal_size_request(size_request_t *r)
566         {
567             r->nMinWidth    = 0;
568             r->nMinHeight   = 0;
569             r->nMaxWidth    = 0;
570             r->nMaxHeight   = 0;
571 
572             font_parameters_t fp;
573             text_parameters_t tp;
574 
575             ISurface *s = pDisplay->create_surface(1, 1);
576             if (s == NULL)
577                 return;
578 
579             sFont.get_parameters(&fp);
580 
581             size_t padding  = 6;
582             size_t n_items  = sItems.size();
583 
584             LSPString text;
585             for (size_t i=0; i<n_items; ++i)
586             {
587                 LSPItem *item = sItems.get(i);
588                 if (item == NULL)
589                     continue;
590 
591                 item->text()->format(&text, this);
592                 if (text.is_empty())
593                     continue;
594 
595                 sFont.get_text_parameters(s, &tp, &text);
596                 if (tp.Width > r->nMaxWidth)
597                     r->nMaxWidth    = tp.Width;
598             }
599 
600             r->nMaxHeight    = fp.Height * n_items + padding;
601 
602             size_request_t vbar;
603             vbar.nMinWidth   = -1;
604             vbar.nMinHeight  = -1;
605             vbar.nMaxWidth   = -1;
606             vbar.nMaxHeight  = -1;
607 
608             sVBar.size_request(&vbar);
609             if (vbar.nMinWidth > 0)
610                 r->nMinWidth    =  vbar.nMinWidth * 2;
611             if (n_items > 2)
612                 n_items         = 4;
613             r->nMinHeight       = fp.Height * n_items + padding*2;
614             if (r->nMaxWidth < r->nMinWidth)
615                 r->nMaxWidth = r->nMinWidth;
616             if (r->nMaxHeight < r->nMinHeight)
617                 r->nMaxHeight = r->nMinHeight;
618 
619             s->destroy();
620             delete s;
621         }
622 
realize(const realize_t * r)623         void LSPListBox::realize(const realize_t *r)
624         {
625             size_request_t hbar, vbar;
626             hbar.nMinWidth   = -1;
627             hbar.nMinHeight  = -1;
628             hbar.nMaxWidth   = -1;
629             hbar.nMaxHeight  = -1;
630             vbar.nMinWidth   = -1;
631             vbar.nMinHeight  = -1;
632             vbar.nMaxWidth   = -1;
633             vbar.nMaxHeight  = -1;
634 
635             sHBar.size_request(&hbar);
636             sVBar.size_request(&vbar);
637 
638             size_t padding = 3;
639 
640             size_t n_items  = sItems.size();
641             if (n_items <= 0)
642                 n_items ++;
643             ssize_t i_height = sFont.height() * n_items + padding*2;
644 
645             bool vb     = ssize_t(r->nHeight) < i_height;
646             bool hb     = false; // TODO
647 
648             realize_t   rh, rv;
649 
650             // Estimate size for vertical and horizontal scroll bars
651             if (vb)
652             {
653                 rv.nWidth   = (vbar.nMinWidth > 0) ? vbar.nMinWidth : 12;
654                 rv.nLeft    = r->nLeft + r->nWidth - rv.nWidth;
655                 rv.nTop     = r->nTop;
656                 rv.nHeight  = r->nHeight;
657             }
658             else
659                 rv.nWidth   = 0;
660 
661             if (hb)
662             {
663                 rh.nHeight  = (hbar.nMinHeight > 0) ? hbar.nMinHeight : 12;
664                 rh.nLeft    = r->nLeft;
665                 rh.nTop     = r->nTop + r->nHeight - rh.nHeight;
666                 rh.nWidth   = r->nWidth;
667             }
668             else
669                 rh.nHeight      = 0;
670 
671             if (vb && hb)
672             {
673                 rv.nHeight -= rh.nHeight;
674                 rh.nWidth  -= rv.nWidth;
675             }
676 
677             if (vb)
678             {
679                 // Realize and show
680                 sVBar.realize(&rv);
681                 sVBar.show();
682                 sVBar.query_draw();
683             }
684             else
685             {
686                 sVBar.hide();
687                 sVBar.set_value(0.0f);
688             }
689 
690             if (hb)
691             {
692                 sHBar.realize(&rh);
693                 sHBar.show();
694                 sHBar.query_draw();
695             }
696             else
697             {
698                 sHBar.hide();
699                 sHBar.set_value(0.0f);
700             }
701 
702             // Remember drawing area parameters
703             sArea.nLeft     = r->nLeft + padding;
704             sArea.nTop      = r->nTop + padding;
705             sArea.nWidth    = r->nWidth - rv.nWidth - padding*2;
706             sArea.nHeight   = r->nHeight - rh.nHeight - padding*2;
707             if (vb)
708                 sArea.nWidth --;
709             if (hb)
710                 sArea.nHeight --;
711 
712             if (vb)
713             {
714                 // Set scrolling parameters
715                 sVBar.set_min_value(0.0f);
716                 sVBar.set_max_value(i_height - r->nHeight + padding*2);
717                 sVBar.set_tiny_step(sFont.height());
718                 sVBar.set_step(sArea.nHeight - sArea.nHeight%ssize_t(sFont.height()));
719             }
720             else
721             {
722                 sVBar.set_min_value(0.0f);
723                 sVBar.set_max_value(0.0f);
724             }
725 
726             // Call parent method
727             LSPComplexWidget::realize(r);
728         }
729     } /* namespace tk */
730 } /* namespace lsp */
731