1 /* 2 * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 #include <stdio.h> 7 8 #include <ScrollView.h> 9 #include <Message.h> 10 #include <Window.h> 11 12 13 static const float kFancyBorderSize = 2; 14 static const float kPlainBorderSize = 1; 15 16 17 BScrollView::BScrollView(const char *name, BView *target, uint32 resizingMode, 18 uint32 flags, bool horizontal, bool vertical, border_style border) 19 : BView(CalcFrame(target, horizontal, vertical, border), name, 20 resizingMode, ModifyFlags(flags, border)), 21 fTarget(target), 22 fHorizontalScrollBar(NULL), 23 fVerticalScrollBar(NULL), 24 fBorder(border), 25 fHighlighted(false) 26 { 27 BRect targetFrame; 28 if (fTarget) { 29 // layout target and add it 30 fTarget->TargetedByScrollView(this); 31 fTarget->MoveTo(B_ORIGIN); 32 33 if (border != B_NO_BORDER) 34 fTarget->MoveBy(BorderSize(border), BorderSize(border)); 35 36 AddChild(fTarget); 37 targetFrame = fTarget->Frame(); 38 } else { 39 // no target specified 40 targetFrame = Bounds(); 41 if (horizontal) 42 targetFrame.bottom -= B_H_SCROLL_BAR_HEIGHT + 1; 43 if (vertical) 44 targetFrame.right -= B_V_SCROLL_BAR_WIDTH + 1; 45 if (border == B_FANCY_BORDER) { 46 targetFrame.bottom--; 47 targetFrame.right--; 48 } 49 } 50 51 if (horizontal) { 52 BRect rect = targetFrame; 53 rect.top = rect.bottom + 1; 54 rect.bottom = rect.top + B_H_SCROLL_BAR_HEIGHT; 55 if (border != B_NO_BORDER || vertical) { 56 // extend scrollbar so that it overlaps one pixel with vertical scrollbar 57 rect.right++; 58 } 59 if (border != B_NO_BORDER) { 60 // the scrollbar draws part of the surrounding frame on the left 61 rect.left--; 62 } 63 fHorizontalScrollBar = new BScrollBar(rect, "_HSB_", fTarget, 0, 1000, B_HORIZONTAL); 64 AddChild(fHorizontalScrollBar); 65 } 66 67 if (vertical) { 68 BRect rect = targetFrame; 69 rect.left = rect.right + 1; 70 rect.right = rect.left + B_V_SCROLL_BAR_WIDTH; 71 if (border != B_NO_BORDER || horizontal) { 72 // extend scrollbar so that it overlaps one pixel with vertical scrollbar 73 rect.bottom++; 74 } 75 if (border != B_NO_BORDER) { 76 // the scrollbar draws part of the surrounding frame on the left 77 rect.top--; 78 } 79 fVerticalScrollBar = new BScrollBar(rect, "_VSB_", fTarget, 0, 1000, B_VERTICAL); 80 AddChild(fVerticalScrollBar); 81 } 82 83 fPreviousWidth = uint16(Bounds().Width()); 84 fPreviousHeight = uint16(Bounds().Height()); 85 } 86 87 88 BScrollView::BScrollView(BMessage *archive) 89 : BView(archive), 90 fHighlighted(false) 91 { 92 int32 border; 93 fBorder = archive->FindInt32("_style", &border) == B_OK ? 94 (border_style)border : B_FANCY_BORDER; 95 96 // In a shallow archive, we may not have a target anymore. We must 97 // be prepared for this case 98 99 // don't confuse our scroll bars with our (eventual) target 100 int32 firstBar = 0; 101 if (!archive->FindBool("_no_target_")) { 102 fTarget = ChildAt(0); 103 firstBar++; 104 } else 105 fTarget = NULL; 106 107 // search for our scroll bars 108 109 fHorizontalScrollBar = NULL; 110 fVerticalScrollBar = NULL; 111 112 BView *view; 113 while ((view = ChildAt(firstBar++)) != NULL) { 114 BScrollBar *bar = dynamic_cast<BScrollBar *>(view); 115 if (bar == NULL) 116 continue; 117 118 if (bar->Orientation() == B_HORIZONTAL) 119 fHorizontalScrollBar = bar; 120 else if (bar->Orientation() == B_VERTICAL) 121 fVerticalScrollBar = bar; 122 } 123 124 fPreviousWidth = uint16(Bounds().Width()); 125 fPreviousHeight = uint16(Bounds().Height()); 126 } 127 128 129 BScrollView::~BScrollView() 130 { 131 } 132 133 134 BArchivable * 135 BScrollView::Instantiate(BMessage *archive) 136 { 137 if (validate_instantiation(archive, "BScrollView")) 138 return new BScrollView(archive); 139 140 return NULL; 141 } 142 143 144 status_t 145 BScrollView::Archive(BMessage *archive, bool deep) const 146 { 147 status_t status = BView::Archive(archive, deep); 148 if (status != B_OK) 149 return status; 150 151 // If this is a deep archive, the BView class will take care 152 // of our children. 153 154 if (status == B_OK && fBorder != B_FANCY_BORDER) 155 status = archive->AddInt32("_style", fBorder); 156 if (status == B_OK && fTarget == NULL) 157 status = archive->AddBool("_no_target_", true); 158 159 // The highlighted state is not archived, but since it is 160 // usually (or should be) used to indicate focus, this 161 // is probably the right thing to do. 162 163 return status; 164 } 165 166 167 void 168 BScrollView::AttachedToWindow() 169 { 170 BView::AttachedToWindow(); 171 172 if ((fHorizontalScrollBar == NULL && fVerticalScrollBar == NULL) 173 || (fHorizontalScrollBar != NULL && fVerticalScrollBar != NULL)) 174 return; 175 176 // If we have only one bar, we need to check if we are in the 177 // bottom right edge of a window with the B_DOCUMENT_LOOK to 178 // adjust the size of the bar to acknowledge the resize knob. 179 180 BRect bounds = ConvertToScreen(Bounds()); 181 BRect windowBounds = Window()->Frame(); 182 183 if (bounds.right - BorderSize(fBorder) != windowBounds.right 184 || bounds.bottom - BorderSize(fBorder) != windowBounds.bottom) 185 return; 186 187 if (fHorizontalScrollBar) 188 fHorizontalScrollBar->ResizeBy(-B_V_SCROLL_BAR_WIDTH, 0); 189 else if (fVerticalScrollBar) 190 fVerticalScrollBar->ResizeBy(0, -B_H_SCROLL_BAR_HEIGHT); 191 } 192 193 194 void 195 BScrollView::DetachedFromWindow() 196 { 197 BView::DetachedFromWindow(); 198 } 199 200 201 void 202 BScrollView::AllAttached() 203 { 204 BView::AllAttached(); 205 } 206 207 208 void 209 BScrollView::AllDetached() 210 { 211 BView::AllDetached(); 212 } 213 214 215 void 216 BScrollView::Draw(BRect updateRect) 217 { 218 if (fBorder == B_PLAIN_BORDER) { 219 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 220 StrokeRect(Bounds()); 221 return; 222 } else if (fBorder != B_FANCY_BORDER) 223 return; 224 225 BRect bounds = Bounds(); 226 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 227 StrokeRect(bounds.InsetByCopy(1, 1)); 228 229 if (fHighlighted) { 230 SetHighColor(ui_color(B_NAVIGATION_BASE_COLOR)); 231 StrokeRect(bounds); 232 } else { 233 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT)); 234 StrokeLine(bounds.LeftBottom(), bounds.LeftTop()); 235 bounds.left++; 236 StrokeLine(bounds.LeftTop(), bounds.RightTop()); 237 238 SetHighColor(ui_color(B_SHINE_COLOR)); 239 StrokeLine(bounds.LeftBottom(), bounds.RightBottom()); 240 bounds.top++; 241 bounds.bottom--; 242 StrokeLine(bounds.RightBottom(), bounds.RightTop()); 243 } 244 } 245 246 247 BScrollBar * 248 BScrollView::ScrollBar(orientation posture) const 249 { 250 if (posture == B_HORIZONTAL) 251 return fHorizontalScrollBar; 252 253 return fVerticalScrollBar; 254 } 255 256 257 void 258 BScrollView::SetBorder(border_style border) 259 { 260 if (fBorder == border) 261 return; 262 263 float offset = BorderSize(fBorder) - BorderSize(border); 264 float resize = 2 * offset; 265 266 float horizontalGap = 0, verticalGap = 0; 267 float change = 0; 268 if (border == B_NO_BORDER || fBorder == B_NO_BORDER) { 269 if (fHorizontalScrollBar != NULL) 270 verticalGap = border != B_NO_BORDER ? 1 : -1; 271 if (fVerticalScrollBar != NULL) 272 horizontalGap = border != B_NO_BORDER ? 1 : -1; 273 274 change = border != B_NO_BORDER ? -1 : 1; 275 if (fHorizontalScrollBar == NULL || fVerticalScrollBar == NULL) 276 change *= 2; 277 } 278 279 fBorder = border; 280 281 int32 savedResizingMode = 0; 282 if (fTarget != NULL) { 283 savedResizingMode = fTarget->ResizingMode(); 284 fTarget->SetResizingMode(B_FOLLOW_NONE); 285 } 286 287 MoveBy(offset, offset); 288 ResizeBy(-resize - horizontalGap, -resize - verticalGap); 289 290 if (fTarget != NULL) { 291 fTarget->MoveBy(-offset, -offset); 292 fTarget->SetResizingMode(savedResizingMode); 293 } 294 295 if (fHorizontalScrollBar != NULL) { 296 fHorizontalScrollBar->MoveBy(-offset - verticalGap, offset + verticalGap); 297 fHorizontalScrollBar->ResizeBy(resize + horizontalGap - change, 0); 298 } 299 if (fVerticalScrollBar != NULL) { 300 fVerticalScrollBar->MoveBy(offset + horizontalGap, -offset - horizontalGap); 301 fVerticalScrollBar->ResizeBy(0, resize + verticalGap - change); 302 } 303 304 SetFlags(ModifyFlags(Flags(), border)); 305 } 306 307 308 border_style 309 BScrollView::Border() const 310 { 311 return fBorder; 312 } 313 314 315 status_t 316 BScrollView::SetBorderHighlighted(bool state) 317 { 318 if (fHighlighted == state) 319 return B_OK; 320 321 if (fBorder != B_FANCY_BORDER) 322 // highlighting only works for B_FANCY_BORDER 323 return B_ERROR; 324 325 fHighlighted = state; 326 327 /* The BeBook describes something like this: 328 329 if (LockLooper()) { 330 Draw(Bounds()); 331 UnlockLooper(); 332 } 333 */ 334 335 // but this is much cleaner, I think: 336 BRect bounds = Bounds(); 337 Invalidate(BRect(bounds.left, bounds.top, bounds.right, bounds.top)); 338 Invalidate(BRect(bounds.left, bounds.top + 1, bounds.left, bounds.bottom - 1)); 339 Invalidate(BRect(bounds.right, bounds.top + 1, bounds.right, bounds.bottom - 1)); 340 Invalidate(BRect(bounds.left, bounds.bottom, bounds.right, bounds.bottom)); 341 342 return B_OK; 343 } 344 345 346 bool 347 BScrollView::IsBorderHighlighted() const 348 { 349 return fHighlighted; 350 } 351 352 353 void 354 BScrollView::SetTarget(BView *target) 355 { 356 if (fTarget == target) 357 return; 358 359 if (fTarget != NULL) { 360 fTarget->TargetedByScrollView(NULL); 361 RemoveChild(fTarget); 362 363 // we are not supposed to delete the view 364 } 365 366 fTarget = target; 367 if (fHorizontalScrollBar != NULL) 368 fHorizontalScrollBar->SetTarget(target); 369 if (fVerticalScrollBar != NULL) 370 fVerticalScrollBar->SetTarget(target); 371 372 if (target != NULL) { 373 target->MoveTo(BorderSize(fBorder), BorderSize(fBorder)); 374 target->TargetedByScrollView(this); 375 376 AddChild(target, ChildAt(0)); 377 // This way, we are making sure that the target will 378 // be added top most in the list (which is important 379 // for unarchiving) 380 } 381 } 382 383 384 BView * 385 BScrollView::Target() const 386 { 387 return fTarget; 388 } 389 390 391 void 392 BScrollView::MessageReceived(BMessage *message) 393 { 394 switch (message->what) { 395 default: 396 BView::MessageReceived(message); 397 } 398 } 399 400 401 void 402 BScrollView::MouseDown(BPoint point) 403 { 404 BView::MouseDown(point); 405 } 406 407 408 void 409 BScrollView::MouseUp(BPoint point) 410 { 411 BView::MouseUp(point); 412 } 413 414 415 void 416 BScrollView::MouseMoved(BPoint point, uint32 code, const BMessage *dragMessage) 417 { 418 BView::MouseMoved(point, code, dragMessage); 419 } 420 421 422 void 423 BScrollView::FrameMoved(BPoint position) 424 { 425 BView::FrameMoved(position); 426 } 427 428 429 void 430 BScrollView::FrameResized(float width, float height) 431 { 432 BView::FrameResized(width, height); 433 434 if (fBorder == B_NO_BORDER) 435 return; 436 437 // changes in width 438 439 BRect bounds = Bounds(); 440 float border = BorderSize(fBorder) - 1; 441 442 if (bounds.Width() > fPreviousWidth) { 443 // invalidate the region between the old and the new right border 444 BRect rect = bounds; 445 rect.left += fPreviousWidth - border; 446 rect.right--; 447 Invalidate(rect); 448 } else if (bounds.Width() < fPreviousWidth) { 449 // invalidate the region of the new right border 450 BRect rect = bounds; 451 rect.left = rect.right - border; 452 Invalidate(rect); 453 } 454 455 // changes in height 456 457 if (bounds.Height() > fPreviousHeight) { 458 // invalidate the region between the old and the new bottom border 459 BRect rect = bounds; 460 rect.top += fPreviousHeight - border; 461 rect.bottom--; 462 Invalidate(rect); 463 } else if (bounds.Height() < fPreviousHeight) { 464 // invalidate the region of the new bottom border 465 BRect rect = bounds; 466 rect.top = rect.bottom - border; 467 Invalidate(rect); 468 } 469 470 fPreviousWidth = uint16(bounds.Width()); 471 fPreviousHeight = uint16(bounds.Height()); 472 } 473 474 475 void 476 BScrollView::ResizeToPreferred() 477 { 478 BView::ResizeToPreferred(); 479 } 480 481 482 void 483 BScrollView::GetPreferredSize(float *_width, float *_height) 484 { 485 BRect frame = CalcFrame(fTarget, fHorizontalScrollBar, fVerticalScrollBar, fBorder); 486 487 if (fTarget != NULL) { 488 float width, height; 489 fTarget->GetPreferredSize(&width, &height); 490 491 frame.right += width - fTarget->Frame().Width(); 492 frame.bottom += height - fTarget->Frame().Height(); 493 } 494 495 if (_width) 496 *_width = frame.Width(); 497 if (_height) 498 *_height = frame.Height(); 499 } 500 501 502 503 504 /** This static method is used to calculate the frame that the 505 * ScrollView will cover depending on the frame of its target 506 * and which border style is used. 507 * It is used in the constructor and at other places. 508 */ 509 510 BRect 511 BScrollView::CalcFrame(BView *target, bool horizontal, bool vertical, border_style border) 512 { 513 BRect frame = target != NULL ? target->Frame() : BRect(0, 0, 80, 80); 514 515 if (vertical) 516 frame.right += B_V_SCROLL_BAR_WIDTH; 517 if (horizontal) 518 frame.bottom += B_H_SCROLL_BAR_HEIGHT; 519 520 float borderSize = BorderSize(border); 521 frame.InsetBy(-borderSize, -borderSize); 522 523 if (borderSize == 0) { 524 if (vertical) 525 frame.right++; 526 if (horizontal) 527 frame.bottom++; 528 } 529 530 return frame; 531 } 532 533 534 /** This method returns the size of the specified border 535 */ 536 537 float 538 BScrollView::BorderSize(border_style border) 539 { 540 if (border == B_FANCY_BORDER) 541 return kFancyBorderSize; 542 if (border == B_PLAIN_BORDER) 543 return kPlainBorderSize; 544 545 return 0; 546 } 547 548 549 /** This method changes the "flags" argument as passed on to 550 * the BView constructor. 551 */ 552 553 int32 554 BScrollView::ModifyFlags(int32 flags, border_style border) 555 { 556 // We either need B_FULL_UPDATE_ON_RESIZE or 557 // B_FRAME_EVENTS if we have to draw a border 558 if (border != B_NO_BORDER) 559 return flags | B_WILL_DRAW | (flags & B_FULL_UPDATE_ON_RESIZE ? 0 : B_FRAME_EVENTS); 560 561 return flags & ~(B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE); 562 } 563 564 565 void 566 BScrollView::WindowActivated(bool active) 567 { 568 BView::WindowActivated(active); 569 } 570 571 572 void 573 BScrollView::MakeFocus(bool state) 574 { 575 BView::MakeFocus(state); 576 } 577 578 579 BHandler * 580 BScrollView::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, int32 form, const char *property) 581 { 582 return BView::ResolveSpecifier(msg, index, specifier, form, property); 583 } 584 585 586 status_t 587 BScrollView::GetSupportedSuites(BMessage *data) 588 { 589 return BView::GetSupportedSuites(data); 590 } 591 592 593 status_t 594 BScrollView::Perform(perform_code d, void *arg) 595 { 596 return BView::Perform(d, arg); 597 } 598 599 600 BScrollView & 601 BScrollView::operator=(const BScrollView &) 602 { 603 return *this; 604 } 605 606 607 /** Although BScrollView::InitObject() was defined in the original ScrollView.h, 608 * it is not exported by the R5 libbe.so, so we don't have to export it as well. 609 */ 610 611 #if 0 612 void 613 BScrollView::InitObject() 614 { 615 } 616 #endif 617 618 619 // #pragma mark - 620 // Reserved virtuals 621 622 623 void BScrollView::_ReservedScrollView1() {} 624 void BScrollView::_ReservedScrollView2() {} 625 void BScrollView::_ReservedScrollView3() {} 626 void BScrollView::_ReservedScrollView4() {} 627 628