DRC | 2ff39b8 | 2011-07-28 08:38:59 +0000 | [diff] [blame] | 1 | // |
| 2 | // "$Id: Fl_Scroll.cxx 8591 2011-04-14 13:21:12Z manolo $" |
| 3 | // |
| 4 | // Scroll widget for the Fast Light Tool Kit (FLTK). |
| 5 | // |
| 6 | // Copyright 1998-2010 by Bill Spitzak and others. |
| 7 | // |
| 8 | // This library is free software; you can redistribute it and/or |
| 9 | // modify it under the terms of the GNU Library General Public |
| 10 | // License as published by the Free Software Foundation; either |
| 11 | // version 2 of the License, or (at your option) any later version. |
| 12 | // |
| 13 | // This library 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 GNU |
| 16 | // Library General Public License for more details. |
| 17 | // |
| 18 | // You should have received a copy of the GNU Library General Public |
| 19 | // License along with this library; if not, write to the Free Software |
| 20 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| 21 | // USA. |
| 22 | // |
| 23 | // Please report all bugs and problems on the following page: |
| 24 | // |
| 25 | // http://www.fltk.org/str.php |
| 26 | // |
| 27 | |
| 28 | #include <FL/Fl.H> |
| 29 | #include <FL/Fl_Tiled_Image.H> |
| 30 | #include <FL/Fl_Scroll.H> |
| 31 | #include <FL/fl_draw.H> |
| 32 | |
| 33 | /** Clear all but the scrollbars... */ |
| 34 | void Fl_Scroll::clear() { |
| 35 | // Note: the scrollbars are removed from the group before calling |
| 36 | // Fl_Group::clear() to take advantage of the optimized widget removal |
| 37 | // and deletion. Finally they are added to Fl_Scroll's group again. This |
| 38 | // is MUCH faster than removing the widgets one by one (STR #2409). |
| 39 | |
| 40 | remove(scrollbar); |
| 41 | remove(hscrollbar); |
| 42 | Fl_Group::clear(); |
| 43 | add(hscrollbar); |
| 44 | add(scrollbar); |
| 45 | } |
| 46 | |
| 47 | /** Insure the scrollbars are the last children */ |
| 48 | void Fl_Scroll::fix_scrollbar_order() { |
| 49 | Fl_Widget** a = (Fl_Widget**)array(); |
| 50 | if (a[children()-1] != &scrollbar) { |
| 51 | int i,j; for (i = j = 0; j < children(); j++) |
| 52 | if (a[j] != &hscrollbar && a[j] != &scrollbar) a[i++] = a[j]; |
| 53 | a[i++] = &hscrollbar; |
| 54 | a[i++] = &scrollbar; |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | // Draw widget's background and children within a specific clip region |
| 59 | // So widget can just redraw damaged parts. |
| 60 | // |
| 61 | void Fl_Scroll::draw_clip(void* v,int X, int Y, int W, int H) { |
| 62 | fl_push_clip(X,Y,W,H); |
| 63 | Fl_Scroll* s = (Fl_Scroll*)v; |
| 64 | // erase background as needed... |
| 65 | switch (s->box()) { |
| 66 | case FL_NO_BOX : |
| 67 | case FL_UP_FRAME : |
| 68 | case FL_DOWN_FRAME : |
| 69 | case FL_THIN_UP_FRAME : |
| 70 | case FL_THIN_DOWN_FRAME : |
| 71 | case FL_ENGRAVED_FRAME : |
| 72 | case FL_EMBOSSED_FRAME : |
| 73 | case FL_BORDER_FRAME : |
| 74 | case _FL_SHADOW_FRAME : |
| 75 | case _FL_ROUNDED_FRAME : |
| 76 | case _FL_OVAL_FRAME : |
| 77 | case _FL_PLASTIC_UP_FRAME : |
| 78 | case _FL_PLASTIC_DOWN_FRAME : |
| 79 | if (s->parent() == (Fl_Group *)s->window() && Fl::scheme_bg_) { |
| 80 | Fl::scheme_bg_->draw(X-(X%((Fl_Tiled_Image *)Fl::scheme_bg_)->image()->w()), |
| 81 | Y-(Y%((Fl_Tiled_Image *)Fl::scheme_bg_)->image()->h()), |
| 82 | W+((Fl_Tiled_Image *)Fl::scheme_bg_)->image()->w(), |
| 83 | H+((Fl_Tiled_Image *)Fl::scheme_bg_)->image()->h()); |
| 84 | break; |
| 85 | } |
| 86 | |
| 87 | default : |
| 88 | fl_color(s->color()); |
| 89 | fl_rectf(X,Y,W,H); |
| 90 | break; |
| 91 | } |
| 92 | Fl_Widget*const* a = s->array(); |
| 93 | for (int i=s->children()-2; i--;) { |
| 94 | Fl_Widget& o = **a++; |
| 95 | s->draw_child(o); |
| 96 | s->draw_outside_label(o); |
| 97 | } |
| 98 | fl_pop_clip(); |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | Calculate visibility/size/position of scrollbars, find children's bounding box. |
| 103 | The \p si paramater will be filled with data from the calculations. |
| 104 | Derived classes can make use of this call to figure out the scrolling area |
| 105 | eg. during resize() handling. |
| 106 | \param[in] si -- ScrollInfo structure |
| 107 | \returns Structure containing the calculated info. |
| 108 | */ |
| 109 | void Fl_Scroll::recalc_scrollbars(ScrollInfo &si) { |
| 110 | |
| 111 | // inner box of widget (excluding scrollbars) |
| 112 | si.innerbox_x = x()+Fl::box_dx(box()); |
| 113 | si.innerbox_y = y()+Fl::box_dy(box()); |
| 114 | si.innerbox_w = w()-Fl::box_dw(box()); |
| 115 | si.innerbox_h = h()-Fl::box_dh(box()); |
| 116 | |
| 117 | // accumulate a bounding box for all the children |
| 118 | si.child_l = si.innerbox_x; |
| 119 | si.child_r = si.innerbox_x; |
| 120 | si.child_b = si.innerbox_y; |
| 121 | si.child_t = si.innerbox_y; |
| 122 | int first = 1; |
| 123 | Fl_Widget*const* a = array(); |
| 124 | for (int i=children()-2; i--;) { |
| 125 | Fl_Widget* o = *a++; |
| 126 | if ( first ) { |
| 127 | first = 0; |
| 128 | si.child_l = o->x(); |
| 129 | si.child_r = o->x()+o->w(); |
| 130 | si.child_b = o->y()+o->h(); |
| 131 | si.child_t = o->y(); |
| 132 | } else { |
| 133 | if (o->x() < si.child_l) si.child_l = o->x(); |
| 134 | if (o->y() < si.child_t) si.child_t = o->y(); |
| 135 | if (o->x()+o->w() > si.child_r) si.child_r = o->x()+o->w(); |
| 136 | if (o->y()+o->h() > si.child_b) si.child_b = o->y()+o->h(); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | // Turn the scrollbars on and off as necessary. |
| 141 | // See if children would fit if we had no scrollbars... |
| 142 | { |
| 143 | int X = si.innerbox_x; |
| 144 | int Y = si.innerbox_y; |
| 145 | int W = si.innerbox_w; |
| 146 | int H = si.innerbox_h; |
| 147 | |
| 148 | si.scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size(); |
| 149 | si.vneeded = 0; |
| 150 | si.hneeded = 0; |
| 151 | if (type() & VERTICAL) { |
| 152 | if ((type() & ALWAYS_ON) || si.child_t < Y || si.child_b > Y+H) { |
| 153 | si.vneeded = 1; |
| 154 | W -= si.scrollsize; |
| 155 | if (scrollbar.align() & FL_ALIGN_LEFT) X += si.scrollsize; |
| 156 | } |
| 157 | } |
| 158 | if (type() & HORIZONTAL) { |
| 159 | if ((type() & ALWAYS_ON) || si.child_l < X || si.child_r > X+W) { |
| 160 | si.hneeded = 1; |
| 161 | H -= si.scrollsize; |
| 162 | if (scrollbar.align() & FL_ALIGN_TOP) Y += si.scrollsize; |
| 163 | // recheck vertical since we added a horizontal scrollbar |
| 164 | if (!si.vneeded && (type() & VERTICAL)) { |
| 165 | if ((type() & ALWAYS_ON) || si.child_t < Y || si.child_b > Y+H) { |
| 166 | si.vneeded = 1; |
| 167 | W -= si.scrollsize; |
| 168 | if (scrollbar.align() & FL_ALIGN_LEFT) X += si.scrollsize; |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | si.innerchild_x = X; |
| 174 | si.innerchild_y = Y; |
| 175 | si.innerchild_w = W; |
| 176 | si.innerchild_h = H; |
| 177 | } |
| 178 | |
| 179 | // calculate hor scrollbar position |
| 180 | si.hscroll_x = si.innerchild_x; |
| 181 | si.hscroll_y = (scrollbar.align() & FL_ALIGN_TOP) |
| 182 | ? si.innerbox_y |
| 183 | : si.innerbox_y + si.innerbox_h - si.scrollsize; |
| 184 | si.hscroll_w = si.innerchild_w; |
| 185 | si.hscroll_h = si.scrollsize; |
| 186 | |
| 187 | // calculate ver scrollbar position |
| 188 | si.vscroll_x = (scrollbar.align() & FL_ALIGN_LEFT) |
| 189 | ? si.innerbox_x |
| 190 | : si.innerbox_x + si.innerbox_w - si.scrollsize; |
| 191 | si.vscroll_y = si.innerchild_y; |
| 192 | si.vscroll_w = si.scrollsize; |
| 193 | si.vscroll_h = si.innerchild_h; |
| 194 | |
| 195 | // calculate h/v scrollbar values (pos/size/first/total) |
| 196 | si.hpos = si.innerchild_x - si.child_l; |
| 197 | si.hsize = si.innerchild_w; |
| 198 | si.hfirst = 0; |
| 199 | si.htotal = si.child_r - si.child_l; |
| 200 | if ( si.hpos < 0 ) { si.htotal += (-si.hpos); si.hfirst = si.hpos; } |
| 201 | |
| 202 | si.vpos = si.innerchild_y - si.child_t; |
| 203 | si.vsize = si.innerchild_h; |
| 204 | si.vfirst = 0; |
| 205 | si.vtotal = si.child_b - si.child_t; |
| 206 | if ( si.vpos < 0 ) { si.vtotal += (-si.vpos); si.vfirst = si.vpos; } |
| 207 | |
| 208 | // printf("DEBUG --- ScrollInfo ---\n"); |
| 209 | // printf("DEBUG scrollsize: %d\n", si.scrollsize); |
| 210 | // printf("DEBUG hneeded, vneeded: %d %d\n", si.hneeded, si.vneeded); |
| 211 | // printf("DEBUG innerbox xywh: %d %d %d %d\n", si.innerbox_x, si.innerbox_y, si.innerbox_w, si.innerbox_h); |
| 212 | // printf("DEBUG innerchild xywh: %d %d %d %d\n", si.innerchild_x, si.innerchild_y, si.innerchild_w, si.innerchild_h); |
| 213 | // printf("DEBUG child lrbt: %d %d %d %d\n", si.child_l, si.child_r, si.child_b, si.child_t); |
| 214 | // printf("DEBUG hscroll xywh: %d %d %d %d\n", si.hscroll_x, si.hscroll_y, si.hscroll_w, si.hscroll_h); |
| 215 | // printf("DEBUG vscroll xywh: %d %d %d %d\n", si.vscroll_x, si.vscroll_y, si.vscroll_w, si.vscroll_h); |
| 216 | // printf("DEBUG horz scroll vals: %d %d %d %d\n", si.hpos, si.hsize, si.hfirst, si.htotal); |
| 217 | // printf("DEBUG vert scroll vals: %d %d %d %d\n", si.vpos, si.vsize, si.vfirst, si.vtotal); |
| 218 | // printf("DEBUG \n"); |
| 219 | } |
| 220 | |
| 221 | /** |
| 222 | Returns the bounding box for the interior of the scrolling area, inside |
| 223 | the scrollbars. |
| 224 | |
| 225 | Currently this is only reliable after draw(), and before any resizing of |
| 226 | the Fl_Scroll or any child widgets occur. |
| 227 | |
| 228 | \todo The visibility of the scrollbars ought to be checked/calculated |
| 229 | outside of the draw() method (STR #1895). |
| 230 | */ |
| 231 | void Fl_Scroll::bbox(int& X, int& Y, int& W, int& H) { |
| 232 | X = x()+Fl::box_dx(box()); |
| 233 | Y = y()+Fl::box_dy(box()); |
| 234 | W = w()-Fl::box_dw(box()); |
| 235 | H = h()-Fl::box_dh(box()); |
| 236 | if (scrollbar.visible()) { |
| 237 | W -= scrollbar.w(); |
| 238 | if (scrollbar.align() & FL_ALIGN_LEFT) X += scrollbar.w(); |
| 239 | } |
| 240 | if (hscrollbar.visible()) { |
| 241 | H -= hscrollbar.h(); |
| 242 | if (scrollbar.align() & FL_ALIGN_TOP) Y += hscrollbar.h(); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | void Fl_Scroll::draw() { |
| 247 | fix_scrollbar_order(); |
| 248 | int X,Y,W,H; bbox(X,Y,W,H); |
| 249 | |
| 250 | uchar d = damage(); |
| 251 | |
| 252 | if (d & FL_DAMAGE_ALL) { // full redraw |
| 253 | draw_box(box(),x(),y(),w(),h(),color()); |
| 254 | draw_clip(this, X, Y, W, H); |
| 255 | } else { |
| 256 | if (d & FL_DAMAGE_SCROLL) { |
| 257 | // scroll the contents: |
| 258 | fl_scroll(X, Y, W, H, oldx-xposition_, oldy-yposition_, draw_clip, this); |
| 259 | |
| 260 | // Erase the background as needed... |
| 261 | Fl_Widget*const* a = array(); |
| 262 | int L, R, T, B; |
| 263 | L = 999999; |
| 264 | R = 0; |
| 265 | T = 999999; |
| 266 | B = 0; |
| 267 | for (int i=children()-2; i--; a++) { |
| 268 | if ((*a)->x() < L) L = (*a)->x(); |
| 269 | if (((*a)->x() + (*a)->w()) > R) R = (*a)->x() + (*a)->w(); |
| 270 | if ((*a)->y() < T) T = (*a)->y(); |
| 271 | if (((*a)->y() + (*a)->h()) > B) B = (*a)->y() + (*a)->h(); |
| 272 | } |
| 273 | if (L > X) draw_clip(this, X, Y, L - X, H); |
| 274 | if (R < (X + W)) draw_clip(this, R, Y, X + W - R, H); |
| 275 | if (T > Y) draw_clip(this, X, Y, W, T - Y); |
| 276 | if (B < (Y + H)) draw_clip(this, X, B, W, Y + H - B); |
| 277 | } |
| 278 | if (d & FL_DAMAGE_CHILD) { // draw damaged children |
| 279 | fl_push_clip(X, Y, W, H); |
| 280 | Fl_Widget*const* a = array(); |
| 281 | for (int i=children()-2; i--;) update_child(**a++); |
| 282 | fl_pop_clip(); |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | // Calculate where scrollbars should go, and draw them |
| 287 | { |
| 288 | ScrollInfo si; |
| 289 | recalc_scrollbars(si); |
| 290 | |
| 291 | // Now that we know what's needed, make it so. |
| 292 | if (si.vneeded && !scrollbar.visible()) { |
| 293 | scrollbar.set_visible(); |
| 294 | d = FL_DAMAGE_ALL; |
| 295 | } |
| 296 | else if (!si.vneeded && scrollbar.visible()) { |
| 297 | scrollbar.clear_visible(); |
| 298 | draw_clip(this, si.vscroll_x, si.vscroll_y, si.vscroll_w, si.vscroll_h); |
| 299 | d = FL_DAMAGE_ALL; |
| 300 | } |
| 301 | if (si.hneeded && !hscrollbar.visible()) { |
| 302 | hscrollbar.set_visible(); |
| 303 | d = FL_DAMAGE_ALL; |
| 304 | } |
| 305 | else if (!si.hneeded && hscrollbar.visible()) { |
| 306 | hscrollbar.clear_visible(); |
| 307 | draw_clip(this, si.hscroll_x, si.hscroll_y, si.hscroll_w, si.hscroll_h); |
| 308 | d = FL_DAMAGE_ALL; |
| 309 | } |
| 310 | else if ( hscrollbar.h() != si.scrollsize || scrollbar.w() != si.scrollsize ) { |
| 311 | // scrollsize changed |
| 312 | d = FL_DAMAGE_ALL; |
| 313 | } |
| 314 | |
| 315 | scrollbar.resize(si.vscroll_x, si.vscroll_y, si.vscroll_w, si.vscroll_h); |
| 316 | oldy = yposition_ = si.vpos; // si.innerchild_y - si.child_t; |
| 317 | scrollbar.value(si.vpos, si.vsize, si.vfirst, si.vtotal); |
| 318 | |
| 319 | hscrollbar.resize(si.hscroll_x, si.hscroll_y, si.hscroll_w, si.hscroll_h); |
| 320 | oldx = xposition_ = si.hpos; // si.innerchild_x - si.child_l; |
| 321 | hscrollbar.value(si.hpos, si.hsize, si.hfirst, si.htotal); |
| 322 | } |
| 323 | |
| 324 | // draw the scrollbars: |
| 325 | if (d & FL_DAMAGE_ALL) { |
| 326 | draw_child(scrollbar); |
| 327 | draw_child(hscrollbar); |
| 328 | if (scrollbar.visible() && hscrollbar.visible()) { |
| 329 | // fill in the little box in the corner |
| 330 | fl_color(color()); |
| 331 | fl_rectf(scrollbar.x(), hscrollbar.y(), scrollbar.w(), hscrollbar.h()); |
| 332 | } |
| 333 | } else { |
| 334 | update_child(scrollbar); |
| 335 | update_child(hscrollbar); |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | void Fl_Scroll::resize(int X, int Y, int W, int H) { |
| 340 | int dx = X-x(), dy = Y-y(); |
| 341 | int dw = W-w(), dh = H-h(); |
| 342 | Fl_Widget::resize(X,Y,W,H); // resize _before_ moving children around |
| 343 | fix_scrollbar_order(); |
| 344 | // move all the children: |
| 345 | Fl_Widget*const* a = array(); |
| 346 | for (int i=children()-2; i--;) { |
| 347 | Fl_Widget* o = *a++; |
| 348 | o->position(o->x()+dx, o->y()+dy); |
| 349 | } |
| 350 | if (dw==0 && dh==0) { |
| 351 | char pad = ( scrollbar.visible() && hscrollbar.visible() ); |
| 352 | char al = ( (scrollbar.align() & FL_ALIGN_LEFT) != 0 ); |
| 353 | char at = ( (scrollbar.align() & FL_ALIGN_TOP) !=0 ); |
| 354 | scrollbar.position(al?X:X+W-scrollbar.w(), (at&&pad)?Y+hscrollbar.h():Y); |
| 355 | hscrollbar.position((al&&pad)?X+scrollbar.w():X, at?Y:Y+H-hscrollbar.h()); |
| 356 | } else { |
| 357 | // FIXME recalculation of scrollbars needs to be moved out fo "draw()" (STR #1895) |
| 358 | redraw(); // need full recalculation of scrollbars |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | /** Moves the contents of the scroll group to a new position.*/ |
| 363 | void Fl_Scroll::scroll_to(int X, int Y) { |
| 364 | int dx = xposition_-X; |
| 365 | int dy = yposition_-Y; |
| 366 | if (!dx && !dy) return; |
| 367 | xposition_ = X; |
| 368 | yposition_ = Y; |
| 369 | Fl_Widget*const* a = array(); |
| 370 | for (int i=children(); i--;) { |
| 371 | Fl_Widget* o = *a++; |
| 372 | if (o == &hscrollbar || o == &scrollbar) continue; |
| 373 | o->position(o->x()+dx, o->y()+dy); |
| 374 | } |
| 375 | if (parent() == (Fl_Group *)window() && Fl::scheme_bg_) damage(FL_DAMAGE_ALL); |
| 376 | else damage(FL_DAMAGE_SCROLL); |
| 377 | } |
| 378 | |
| 379 | void Fl_Scroll::hscrollbar_cb(Fl_Widget* o, void*) { |
| 380 | Fl_Scroll* s = (Fl_Scroll*)(o->parent()); |
| 381 | s->scroll_to(int(((Fl_Scrollbar*)o)->value()), s->yposition()); |
| 382 | } |
| 383 | |
| 384 | void Fl_Scroll::scrollbar_cb(Fl_Widget* o, void*) { |
| 385 | Fl_Scroll* s = (Fl_Scroll*)(o->parent()); |
| 386 | s->scroll_to(s->xposition(), int(((Fl_Scrollbar*)o)->value())); |
| 387 | } |
| 388 | /** |
| 389 | Creates a new Fl_Scroll widget using the given position, |
| 390 | size, and label string. The default boxtype is FL_NO_BOX. |
| 391 | <P>The destructor <I>also deletes all the children</I>. This allows a |
| 392 | whole tree to be deleted at once, without having to keep a pointer to |
| 393 | all the children in the user code. A kludge has been done so the |
| 394 | Fl_Scroll and all of its children can be automatic (local) |
| 395 | variables, but you must declare the Fl_Scroll<I>first</I>, so |
| 396 | that it is destroyed last. |
| 397 | */ |
| 398 | Fl_Scroll::Fl_Scroll(int X,int Y,int W,int H,const char* L) |
| 399 | : Fl_Group(X,Y,W,H,L), |
| 400 | scrollbar(X+W-Fl::scrollbar_size(),Y, |
| 401 | Fl::scrollbar_size(),H-Fl::scrollbar_size()), |
| 402 | hscrollbar(X,Y+H-Fl::scrollbar_size(), |
| 403 | W-Fl::scrollbar_size(),Fl::scrollbar_size()) { |
| 404 | type(BOTH); |
| 405 | xposition_ = oldx = 0; |
| 406 | yposition_ = oldy = 0; |
| 407 | scrollbar_size_ = 0; |
| 408 | hscrollbar.type(FL_HORIZONTAL); |
| 409 | hscrollbar.callback(hscrollbar_cb); |
| 410 | scrollbar.callback(scrollbar_cb); |
| 411 | } |
| 412 | |
| 413 | int Fl_Scroll::handle(int event) { |
| 414 | fix_scrollbar_order(); |
| 415 | return Fl_Group::handle(event); |
| 416 | } |
| 417 | |
| 418 | // |
| 419 | // End of "$Id: Fl_Scroll.cxx 8591 2011-04-14 13:21:12Z manolo $". |
| 420 | // |