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 || Window()->Look() != B_DOCUMENT_WINDOW_LOOK) 175 return; 176 177 // If we have only one bar, we need to check if we are in the 178 // bottom right edge of a window with the B_DOCUMENT_LOOK to 179 // adjust the size of the bar to acknowledge the resize knob. 180 181 BRect bounds = ConvertToScreen(Bounds()); 182 BRect windowBounds = Window()->Frame(); 183 184 if (bounds.right - BorderSize(fBorder) != windowBounds.right 185 || bounds.bottom - BorderSize(fBorder) != windowBounds.bottom) 186 return; 187 188 if (fHorizontalScrollBar) 189 fHorizontalScrollBar->ResizeBy(-B_V_SCROLL_BAR_WIDTH, 0); 190 else if (fVerticalScrollBar) 191 fVerticalScrollBar->ResizeBy(0, -B_H_SCROLL_BAR_HEIGHT); 192 } 193 194 195 void 196 BScrollView::DetachedFromWindow() 197 { 198 BView::DetachedFromWindow(); 199 } 200 201 202 void 203 BScrollView::AllAttached() 204 { 205 BView::AllAttached(); 206 } 207 208 209 void 210 BScrollView::AllDetached() 211 { 212 BView::AllDetached(); 213 } 214 215 216 void 217 BScrollView::Draw(BRect updateRect) 218 { 219 if (fBorder == B_PLAIN_BORDER) { 220 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 221 StrokeRect(Bounds()); 222 return; 223 } else if (fBorder != B_FANCY_BORDER) 224 return; 225 226 BRect bounds = Bounds(); 227 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 228 StrokeRect(bounds.InsetByCopy(1, 1)); 229 230 if (fHighlighted && Window()->IsActive()) { 231 SetHighColor(ui_color(B_NAVIGATION_BASE_COLOR)); 232 StrokeRect(bounds); 233 } else { 234 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT)); 235 StrokeLine(bounds.LeftBottom(), bounds.LeftTop()); 236 bounds.left++; 237 StrokeLine(bounds.LeftTop(), bounds.RightTop()); 238 239 SetHighColor(ui_color(B_SHINE_COLOR)); 240 StrokeLine(bounds.LeftBottom(), bounds.RightBottom()); 241 bounds.top++; 242 bounds.bottom--; 243 StrokeLine(bounds.RightBottom(), bounds.RightTop()); 244 } 245 } 246 247 248 BScrollBar * 249 BScrollView::ScrollBar(orientation posture) const 250 { 251 if (posture == B_HORIZONTAL) 252 return fHorizontalScrollBar; 253 254 return fVerticalScrollBar; 255 } 256 257 258 void 259 BScrollView::SetBorder(border_style border) 260 { 261 if (fBorder == border) 262 return; 263 264 float offset = BorderSize(fBorder) - BorderSize(border); 265 float resize = 2 * offset; 266 267 float horizontalGap = 0, verticalGap = 0; 268 float change = 0; 269 if (border == B_NO_BORDER || fBorder == B_NO_BORDER) { 270 if (fHorizontalScrollBar != NULL) 271 verticalGap = border != B_NO_BORDER ? 1 : -1; 272 if (fVerticalScrollBar != NULL) 273 horizontalGap = border != B_NO_BORDER ? 1 : -1; 274 275 change = border != B_NO_BORDER ? -1 : 1; 276 if (fHorizontalScrollBar == NULL || fVerticalScrollBar == NULL) 277 change *= 2; 278 } 279 280 fBorder = border; 281 282 int32 savedResizingMode = 0; 283 if (fTarget != NULL) { 284 savedResizingMode = fTarget->ResizingMode(); 285 fTarget->SetResizingMode(B_FOLLOW_NONE); 286 } 287 288 MoveBy(offset, offset); 289 ResizeBy(-resize - horizontalGap, -resize - verticalGap); 290 291 if (fTarget != NULL) { 292 fTarget->MoveBy(-offset, -offset); 293 fTarget->SetResizingMode(savedResizingMode); 294 } 295 296 if (fHorizontalScrollBar != NULL) { 297 fHorizontalScrollBar->MoveBy(-offset - verticalGap, offset + verticalGap); 298 fHorizontalScrollBar->ResizeBy(resize + horizontalGap - change, 0); 299 } 300 if (fVerticalScrollBar != NULL) { 301 fVerticalScrollBar->MoveBy(offset + horizontalGap, -offset - horizontalGap); 302 fVerticalScrollBar->ResizeBy(0, resize + verticalGap - change); 303 } 304 305 SetFlags(ModifyFlags(Flags(), border)); 306 } 307 308 309 border_style 310 BScrollView::Border() const 311 { 312 return fBorder; 313 } 314 315 316 status_t 317 BScrollView::SetBorderHighlighted(bool state) 318 { 319 if (fHighlighted == state) 320 return B_OK; 321 322 if (fBorder != B_FANCY_BORDER) 323 // highlighting only works for B_FANCY_BORDER 324 return B_ERROR; 325 326 fHighlighted = state; 327 328 /* The BeBook describes something like this: 329 330 if (LockLooper()) { 331 Draw(Bounds()); 332 UnlockLooper(); 333 } 334 */ 335 336 // but this is much cleaner, I think: 337 BRect bounds = Bounds(); 338 Invalidate(BRect(bounds.left, bounds.top, bounds.right, bounds.top)); 339 Invalidate(BRect(bounds.left, bounds.top + 1, bounds.left, bounds.bottom - 1)); 340 Invalidate(BRect(bounds.right, bounds.top + 1, bounds.right, bounds.bottom - 1)); 341 Invalidate(BRect(bounds.left, bounds.bottom, bounds.right, bounds.bottom)); 342 343 return B_OK; 344 } 345 346 347 bool 348 BScrollView::IsBorderHighlighted() const 349 { 350 return fHighlighted; 351 } 352 353 354 void 355 BScrollView::SetTarget(BView *target) 356 { 357 if (fTarget == target) 358 return; 359 360 if (fTarget != NULL) { 361 fTarget->TargetedByScrollView(NULL); 362 RemoveChild(fTarget); 363 364 // we are not supposed to delete the view 365 } 366 367 fTarget = target; 368 if (fHorizontalScrollBar != NULL) 369 fHorizontalScrollBar->SetTarget(target); 370 if (fVerticalScrollBar != NULL) 371 fVerticalScrollBar->SetTarget(target); 372 373 if (target != NULL) { 374 target->MoveTo(BorderSize(fBorder), BorderSize(fBorder)); 375 target->TargetedByScrollView(this); 376 377 AddChild(target, ChildAt(0)); 378 // This way, we are making sure that the target will 379 // be added top most in the list (which is important 380 // for unarchiving) 381 } 382 } 383 384 385 BView * 386 BScrollView::Target() const 387 { 388 return fTarget; 389 } 390 391 392 void 393 BScrollView::MessageReceived(BMessage *message) 394 { 395 switch (message->what) { 396 default: 397 BView::MessageReceived(message); 398 } 399 } 400 401 402 void 403 BScrollView::MouseDown(BPoint point) 404 { 405 BView::MouseDown(point); 406 } 407 408 409 void 410 BScrollView::MouseUp(BPoint point) 411 { 412 BView::MouseUp(point); 413 } 414 415 416 void 417 BScrollView::MouseMoved(BPoint point, uint32 code, const BMessage *dragMessage) 418 { 419 BView::MouseMoved(point, code, dragMessage); 420 } 421 422 423 void 424 BScrollView::FrameMoved(BPoint position) 425 { 426 BView::FrameMoved(position); 427 } 428 429 430 void 431 BScrollView::FrameResized(float width, float height) 432 { 433 BView::FrameResized(width, height); 434 435 if (fBorder == B_NO_BORDER) 436 return; 437 438 // changes in width 439 440 BRect bounds = Bounds(); 441 float border = BorderSize(fBorder) - 1; 442 443 if (bounds.Width() > fPreviousWidth) { 444 // invalidate the region between the old and the new right border 445 BRect rect = bounds; 446 rect.left += fPreviousWidth - border; 447 rect.right--; 448 Invalidate(rect); 449 } else if (bounds.Width() < fPreviousWidth) { 450 // invalidate the region of the new right border 451 BRect rect = bounds; 452 rect.left = rect.right - border; 453 Invalidate(rect); 454 } 455 456 // changes in height 457 458 if (bounds.Height() > fPreviousHeight) { 459 // invalidate the region between the old and the new bottom border 460 BRect rect = bounds; 461 rect.top += fPreviousHeight - border; 462 rect.bottom--; 463 Invalidate(rect); 464 } else if (bounds.Height() < fPreviousHeight) { 465 // invalidate the region of the new bottom border 466 BRect rect = bounds; 467 rect.top = rect.bottom - border; 468 Invalidate(rect); 469 } 470 471 fPreviousWidth = uint16(bounds.Width()); 472 fPreviousHeight = uint16(bounds.Height()); 473 } 474 475 476 void 477 BScrollView::ResizeToPreferred() 478 { 479 BView::ResizeToPreferred(); 480 } 481 482 483 void 484 BScrollView::GetPreferredSize(float *_width, float *_height) 485 { 486 BRect frame = CalcFrame(fTarget, fHorizontalScrollBar, fVerticalScrollBar, fBorder); 487 488 if (fTarget != NULL) { 489 float width, height; 490 fTarget->GetPreferredSize(&width, &height); 491 492 frame.right += width - fTarget->Frame().Width(); 493 frame.bottom += height - fTarget->Frame().Height(); 494 } 495 496 if (_width) 497 *_width = frame.Width(); 498 if (_height) 499 *_height = frame.Height(); 500 } 501 502 503 504 505 /** This static method is used to calculate the frame that the 506 * ScrollView will cover depending on the frame of its target 507 * and which border style is used. 508 * It is used in the constructor and at other places. 509 */ 510 511 BRect 512 BScrollView::CalcFrame(BView *target, bool horizontal, bool vertical, border_style border) 513 { 514 BRect frame = target != NULL ? target->Frame() : BRect(0, 0, 80, 80); 515 516 if (vertical) 517 frame.right += B_V_SCROLL_BAR_WIDTH; 518 if (horizontal) 519 frame.bottom += B_H_SCROLL_BAR_HEIGHT; 520 521 float borderSize = BorderSize(border); 522 frame.InsetBy(-borderSize, -borderSize); 523 524 if (borderSize == 0) { 525 if (vertical) 526 frame.right++; 527 if (horizontal) 528 frame.bottom++; 529 } 530 531 return frame; 532 } 533 534 535 /** This method returns the size of the specified border 536 */ 537 538 float 539 BScrollView::BorderSize(border_style border) 540 { 541 if (border == B_FANCY_BORDER) 542 return kFancyBorderSize; 543 if (border == B_PLAIN_BORDER) 544 return kPlainBorderSize; 545 546 return 0; 547 } 548 549 550 /** This method changes the "flags" argument as passed on to 551 * the BView constructor. 552 */ 553 554 int32 555 BScrollView::ModifyFlags(int32 flags, border_style border) 556 { 557 // We either need B_FULL_UPDATE_ON_RESIZE or 558 // B_FRAME_EVENTS if we have to draw a border 559 if (border != B_NO_BORDER) 560 return flags | B_WILL_DRAW | (flags & B_FULL_UPDATE_ON_RESIZE ? 0 : B_FRAME_EVENTS); 561 562 return flags & ~(B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE); 563 } 564 565 566 void 567 BScrollView::WindowActivated(bool active) 568 { 569 if (fHighlighted) 570 Invalidate(); 571 572 BView::WindowActivated(active); 573 } 574 575 576 void 577 BScrollView::MakeFocus(bool state) 578 { 579 BView::MakeFocus(state); 580 } 581 582 583 BHandler * 584 BScrollView::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, int32 form, const char *property) 585 { 586 return BView::ResolveSpecifier(msg, index, specifier, form, property); 587 } 588 589 590 status_t 591 BScrollView::GetSupportedSuites(BMessage *data) 592 { 593 return BView::GetSupportedSuites(data); 594 } 595 596 597 status_t 598 BScrollView::Perform(perform_code d, void *arg) 599 { 600 return BView::Perform(d, arg); 601 } 602 603 604 BScrollView & 605 BScrollView::operator=(const BScrollView &) 606 { 607 return *this; 608 } 609 610 611 /** Although BScrollView::InitObject() was defined in the original ScrollView.h, 612 * it is not exported by the R5 libbe.so, so we don't have to export it as well. 613 */ 614 615 #if 0 616 void 617 BScrollView::InitObject() 618 { 619 } 620 #endif 621 622 623 // #pragma mark - 624 // Reserved virtuals 625 626 627 void BScrollView::_ReservedScrollView1() {} 628 void BScrollView::_ReservedScrollView2() {} 629 void BScrollView::_ReservedScrollView3() {} 630 void BScrollView::_ReservedScrollView4() {} 631 632