1 // ValControl.cpp 2 3 #include "ValControl.h" 4 #include "ValControlSegment.h" 5 6 #include "TextControlFloater.h" 7 8 #include <Debug.h> 9 #include <String.h> 10 #include <Window.h> 11 12 #include <algorithm> 13 #include <functional> 14 #include <cstdio> 15 16 using namespace std; 17 18 __USE_CORTEX_NAMESPACE 19 20 21 const float ValControl::fSegmentPadding = 2.0; 22 23 // the decimal point covers one more pixel x and y-ward: 24 const float ValControl::fDecimalPointWidth = 2.0; 25 const float ValControl::fDecimalPointHeight = 2.0; 26 27 28 /*protected*/ 29 ValControl::ValControl(BRect frame, const char* name, const char* label, 30 BMessage* message, align_mode alignMode, align_flags alignFlags, 31 update_mode updateMode, bool backBuffer) 32 : BControl(frame, name, label, message, B_FOLLOW_TOP|B_FOLLOW_LEFT, 33 B_WILL_DRAW|B_FRAME_EVENTS), 34 fDirty(true), 35 fUpdateMode(updateMode), 36 fLabelFont(be_bold_font), 37 fValueFont(be_bold_font), 38 fAlignMode(alignMode), 39 fAlignFlags(alignFlags), 40 fOrigBounds(Bounds()), 41 fHaveBackBuffer(backBuffer), 42 fBackBuffer(NULL), 43 fBackBufferView(NULL) 44 { 45 if (fHaveBackBuffer) 46 _AllocBackBuffer(frame.Width(), frame.Height()); 47 48 // m_font.SetSize(13.0); 49 // rgb_color red = {255,0,0,255}; 50 // SetViewColor(red); 51 } 52 53 54 ValControl::~ValControl() 55 { 56 delete fBackBuffer; 57 } 58 59 60 ValControl::update_mode 61 ValControl::updateMode() const 62 { 63 return fUpdateMode; 64 } 65 66 67 void 68 ValControl::setUpdateMode(update_mode mode) 69 { 70 fUpdateMode = mode; 71 } 72 73 74 const BFont* 75 ValControl::labelFont() const 76 { 77 return &fLabelFont; 78 } 79 80 81 void 82 ValControl::setLabelFont(const BFont* labelFont) 83 { 84 fLabelFont = labelFont; 85 // inform label segments 86 _InvalidateAll(); 87 } 88 89 90 const BFont* 91 ValControl::valueFont() const 92 { 93 return &fValueFont; 94 } 95 96 97 void 98 ValControl::setValueFont(const BFont* valueFont) 99 { 100 fValueFont = valueFont; 101 102 // inform value segments 103 for (int n = CountEntries(); n > 0; --n) { 104 const ValCtrlLayoutEntry& e = _EntryAt(n-1); 105 if (e.type != ValCtrlLayoutEntry::SEGMENT_ENTRY) 106 continue; 107 108 ValControlSegment* s = dynamic_cast<ValControlSegment*>(e.pView); 109 ASSERT(s); 110 s->SetFont(&fValueFont); 111 s->fontChanged(&fValueFont); 112 } 113 } 114 115 116 float 117 ValControl::baselineOffset() const 118 { 119 font_height h; 120 be_plain_font->GetHeight(&h); 121 return ceil(h.ascent); 122 } 123 124 125 float 126 ValControl::segmentPadding() const 127 { 128 return fSegmentPadding; 129 } 130 131 132 BView* 133 ValControl::backBufferView() const 134 { 135 return fBackBufferView; 136 } 137 138 139 BBitmap* 140 ValControl::backBuffer() const 141 { 142 return fBackBuffer; 143 } 144 145 146 void 147 ValControl::dump() 148 { 149 #if defined(DEBUG) 150 BRect f = Frame(); 151 152 PRINT(( 153 "*** ValControl::dump():\n" 154 " FRAME (%.1f,%.1f)-(%.1f,%.1f)\n" 155 " ENTRIES:\n", 156 f.left, f.top, f.right, f.bottom)); 157 158 for (layout_set::const_iterator it = fLayoutSet.begin(); 159 it != fLayoutSet.end(); ++it) { 160 const ValCtrlLayoutEntry& e = *it; 161 switch (e.type) { 162 case ValCtrlLayoutEntry::SEGMENT_ENTRY: 163 PRINT((" Segment ")); 164 break; 165 166 case ValCtrlLayoutEntry::VIEW_ENTRY: 167 PRINT((" View ")); 168 break; 169 170 case ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY: 171 PRINT((" Decimal Point ")); 172 break; 173 174 default: 175 PRINT((" ??? ")); 176 break; 177 } 178 179 PRINT(("\n cached frame (%.1f,%.1f)-(%.1f,%.1f) + pad(%.1f)\n", 180 e.frame.left, e.frame.top, e.frame.right, e.frame.bottom, 181 e.fPadding)); 182 183 if (e.type == ValCtrlLayoutEntry::SEGMENT_ENTRY 184 || e.type == ValCtrlLayoutEntry::VIEW_ENTRY) { 185 if (e.pView) { 186 PRINT((" real frame (%.1f,%.1f)-(%.1f,%.1f)\n\n", 187 e.pView->Frame().left, e.pView->Frame().top, 188 e.pView->Frame().right, e.pView->Frame().bottom)); 189 } else 190 PRINT((" (no view!)\n\n")); 191 } 192 } 193 PRINT(("\n")); 194 #endif 195 } 196 197 198 void 199 ValControl::SetEnabled(bool enabled) 200 { 201 // redraw if enabled-state changes 202 _Inherited::SetEnabled(enabled); 203 204 _InvalidateAll(); 205 } 206 207 208 void 209 ValControl::_InvalidateAll() 210 { 211 Invalidate(); 212 int c = CountChildren(); 213 for (int n = 0; n < c; ++n) 214 ChildAt(n)->Invalidate(); 215 } 216 217 218 void 219 ValControl::AttachedToWindow() 220 { 221 // adopt parent view's color 222 if (Parent()) 223 SetViewColor(Parent()->ViewColor()); 224 } 225 226 227 void 228 ValControl::AllAttached() 229 { 230 // move children to requested positions 231 BWindow* pWnd = Window(); 232 pWnd->BeginViewTransaction(); 233 234 for_each(fLayoutSet.begin(), fLayoutSet.end(), 235 ptr_fun(&ValCtrlLayoutEntry::InitChildFrame)); // +++++? 236 237 pWnd->EndViewTransaction(); 238 } 239 240 241 //! Paint decorations (& decimal point) 242 void 243 ValControl::Draw(BRect updateRect) 244 { 245 // draw lightweight entries: 246 for (unsigned int n = 0; n < fLayoutSet.size(); n++) { 247 if (fLayoutSet[n].type == ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY) 248 drawDecimalPoint(fLayoutSet[n]); 249 } 250 } 251 252 253 void 254 ValControl::drawDecimalPoint(ValCtrlLayoutEntry& e) 255 { 256 rgb_color dark = {0, 0, 0, 255}; 257 rgb_color med = {200, 200, 200, 255}; 258 // rgb_color light = {244,244,244,255}; 259 260 BPoint center; 261 center.x = e.frame.left + 1; 262 center.y = baselineOffset() - 1; 263 264 SetHighColor(dark); 265 StrokeLine(center, center); 266 SetHighColor(med); 267 StrokeLine(center - BPoint(0, 1), center + BPoint(1, 0)); 268 StrokeLine(center - BPoint(1, 0), center + BPoint(0, 1)); 269 270 // SetHighColor(light); 271 // StrokeLine(center+BPoint(-1,1), center+BPoint(-1,1)); 272 // StrokeLine(center+BPoint(1,1), center+BPoint(1,1)); 273 // StrokeLine(center+BPoint(-1,-1), center+BPoint(-1,-1)); 274 // StrokeLine(center+BPoint(1,-1), center+BPoint(1,-1)); 275 } 276 277 278 void 279 ValControl::FrameResized(float width, float height) 280 { 281 _Inherited::FrameResized(width,height); 282 if (fHaveBackBuffer) 283 _AllocBackBuffer(width, height); 284 // 285 // PRINT(( 286 // "# ValControl::FrameResized(): %.1f, %.1f\n", 287 // width, height)); 288 } 289 290 291 void 292 ValControl::GetPreferredSize(float* outWidth, float* outHeight) 293 { 294 ASSERT(fLayoutSet.size() > 0); 295 296 *outWidth = 297 fLayoutSet.back().frame.right - 298 fLayoutSet.front().frame.left; 299 300 *outHeight = 0; 301 for(layout_set::const_iterator it = fLayoutSet.begin(); 302 it != fLayoutSet.end(); ++it) { 303 if((*it).frame.Height() > *outHeight) 304 *outHeight = (*it).frame.Height(); 305 } 306 // 307 // PRINT(( 308 // "# ValControl::GetPreferredSize(): %.1f, %.1f\n", 309 // *outWidth, *outHeight)); 310 } 311 312 313 void 314 ValControl::MakeFocus(bool focused) 315 { 316 _Inherited::MakeFocus(focused); 317 318 // +++++ only the underline needs to be redrawn 319 _InvalidateAll(); 320 } 321 322 323 void 324 ValControl::MouseDown(BPoint where) 325 { 326 MakeFocus(true); 327 } 328 329 330 void 331 ValControl::MessageReceived(BMessage* message) 332 { 333 status_t err; 334 const char* stringValue; 335 336 // PRINT(( 337 // "ValControl::MessageReceived():\n")); 338 // message->PrintToStream(); 339 340 switch (message->what) { 341 case M_SET_VALUE: 342 err = message->FindString("_value", &stringValue); 343 if(err < B_OK) { 344 PRINT(( 345 "! ValControl::MessageReceived(): no _value found!\n")); 346 break; 347 } 348 349 // set from string 350 err = setValueFrom(stringValue); 351 if (err < B_OK) { 352 PRINT(( 353 "! ValControl::MessageReceived(): setValueFrom('%s'):\n" 354 " %s\n", 355 stringValue, 356 strerror(err))); 357 } 358 359 // +++++ broadcast new value +++++ [23aug99] 360 break; 361 362 case M_GET_VALUE: // +++++ 363 break; 364 365 default: 366 _Inherited::MessageReceived(message); 367 } 368 } 369 370 371 // -------------------------------------------------------- // 372 // archiving/instantiation 373 // -------------------------------------------------------- // 374 375 ValControl::ValControl(BMessage* archive) 376 : BControl(archive), 377 fDirty(true) 378 { 379 // fetch parameters 380 archive->FindInt32("updateMode", (int32*)&fUpdateMode); 381 archive->FindInt32("alignMode", (int32*)&fAlignMode); 382 archive->FindInt32("alignFlags", (int32*)&fAlignFlags); 383 384 // original bounds 385 archive->FindRect("origBounds", &fOrigBounds); 386 } 387 388 389 status_t 390 ValControl::Archive(BMessage* archive, bool deep) const 391 { 392 status_t err = _Inherited::Archive(archive, deep); 393 394 // write parameters 395 if (err == B_OK) 396 err = archive->AddInt32("updateMode", (int32)fUpdateMode); 397 if (err == B_OK) 398 err = archive->AddInt32("alignMode", (int32)fAlignMode); 399 if (err == B_OK) 400 err = archive->AddInt32("alignFlags", (int32)fAlignFlags); 401 if (err == B_OK) 402 err = archive->AddRect("origBounds", fOrigBounds); 403 if (err < B_OK) 404 return err; 405 406 // write layout set? 407 if (!deep) 408 return B_OK; 409 410 // yes; spew it: 411 for (layout_set::const_iterator it = fLayoutSet.begin(); 412 it != fLayoutSet.end(); it++) { 413 414 // archive entry 415 BMessage layoutSet; 416 ASSERT((*it).pView); 417 err = (*it).pView->Archive(&layoutSet, true); 418 ASSERT(err == B_OK); 419 420 // write it 421 archive->AddMessage("layoutSet", &layoutSet); 422 } 423 424 return B_OK; 425 } 426 427 428 // -------------------------------------------------------- // 429 // internal operations 430 // -------------------------------------------------------- // 431 432 // add segment view (which is responsible for generating its 433 // own ValCtrlLayoutEntry) 434 void 435 ValControl::_Add(ValControlSegment* segment, entry_location from, 436 uint16 distance) 437 { 438 BWindow* pWnd = Window(); 439 if(pWnd) 440 pWnd->BeginViewTransaction(); 441 442 AddChild(segment); 443 444 segment->SetFont(&fValueFont); 445 segment->fontChanged(&fValueFont); 446 447 uint16 nIndex = _LocationToIndex(from, distance); 448 ValCtrlLayoutEntry entry = segment->makeLayoutEntry(); 449 _InsertEntry(entry, nIndex); 450 // linkSegment(segment, nIndex); 451 452 if (pWnd) 453 pWnd->EndViewTransaction(); 454 } 455 456 457 // add general view (manipulator, label, etc.) 458 // the entry's frame rectangle will be filled in 459 void 460 ValControl::_Add(ValCtrlLayoutEntry& entry, entry_location from) 461 { 462 BWindow* window = Window(); 463 if (window) 464 window->BeginViewTransaction(); 465 466 if (entry.pView) 467 AddChild(entry.pView); 468 469 uint16 index = _LocationToIndex(from, 0); 470 _InsertEntry(entry, index); 471 472 if (window) 473 window->EndViewTransaction(); 474 } 475 476 477 // access child-view ValCtrlLayoutEntry 478 // (_IndexOf returns index from left) 479 const ValCtrlLayoutEntry& 480 ValControl::_EntryAt(entry_location from, uint16 distance) const 481 { 482 uint16 nIndex = _LocationToIndex(from, distance); 483 ASSERT(nIndex < fLayoutSet.size()); 484 return fLayoutSet[nIndex]; 485 } 486 487 488 const ValCtrlLayoutEntry& 489 ValControl::_EntryAt(uint16 offset) const 490 { 491 uint16 nIndex = _LocationToIndex(FROM_LEFT, offset); 492 ASSERT(nIndex < fLayoutSet.size()); 493 return fLayoutSet[nIndex]; 494 } 495 496 497 uint16 498 ValControl::_IndexOf(BView* child) const 499 { 500 for (uint16 n = 0; n < fLayoutSet.size(); n++) { 501 if (fLayoutSet[n].pView == child) 502 return n; 503 } 504 505 ASSERT(!"shouldn't be here"); 506 return 0; 507 } 508 509 510 uint16 511 ValControl::CountEntries() const 512 { 513 return fLayoutSet.size(); 514 } 515 516 517 // pop up keyboard input field +++++ 518 void 519 ValControl::showEditField() 520 { 521 BString valueString; 522 523 #if defined(DEBUG) 524 status_t err = getString(valueString); 525 ASSERT(err == B_OK); 526 #endif // DEBUG 527 528 BRect f = Bounds().OffsetByCopy(4.0, -4.0); 529 ConvertToScreen(&f); 530 //PRINT(( 531 //"# ValControl::showEditField(): base bounds (%.1f, %.1f)-(%.1f,%.1f)\n", 532 //f.left, f.top, f.right, f.bottom)); 533 534 new TextControlFloater(f, B_ALIGN_RIGHT, &fValueFont, valueString.String(), 535 BMessenger(this), new BMessage(M_SET_VALUE)); 536 // TextControlFloater embeds new value 537 // in message: _value (string) +++++ DO NOT HARDCODE 538 } 539 540 541 //! (Re-)initialize backbuffer 542 void 543 ValControl::_AllocBackBuffer(float width, float height) 544 { 545 ASSERT(fHaveBackBuffer); 546 if (fBackBuffer && fBackBuffer->Bounds().Width() >= width 547 && fBackBuffer->Bounds().Height() >= height) 548 return; 549 550 if (fBackBuffer) { 551 delete fBackBuffer; 552 fBackBuffer = NULL; 553 fBackBufferView = NULL; 554 } 555 556 BRect bounds(0, 0, width, height); 557 fBackBuffer = new BBitmap(bounds, B_RGB32, true); 558 fBackBufferView = new BView(bounds, "back", B_FOLLOW_NONE, B_WILL_DRAW); 559 fBackBuffer->AddChild(fBackBufferView); 560 } 561 562 563 // ref'd view must already be a child +++++ 564 // (due to GetPreferredSize implementation in segment views) 565 void 566 ValControl::_InsertEntry(ValCtrlLayoutEntry& entry, uint16 index) 567 { 568 // view ptr must be 0, or a ValControlSegment that's already a child 569 ValControlSegment* pSeg = dynamic_cast<ValControlSegment*>(entry.pView); 570 if (entry.pView) 571 ASSERT(pSeg); 572 if (pSeg) 573 ASSERT(this == pSeg->Parent()); 574 575 // entry must be at one side or the other: 576 ASSERT(!index || index == fLayoutSet.size()); 577 578 // figure padding 579 bool bNeedsPadding = 580 !(entry.flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING || 581 ((index - 1 >= 0 && 582 fLayoutSet[index - 1].flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING)) || 583 ((index + 1 < static_cast<uint16>(fLayoutSet.size()) && 584 fLayoutSet[index + 1].flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING))); 585 586 entry.fPadding = (bNeedsPadding) ? fSegmentPadding : 0.0; 587 588 // fetch (and grant) requested frame size 589 BRect frame(0, 0, 0, 0); 590 if (pSeg) 591 pSeg->GetPreferredSize(&frame.right, &frame.bottom); 592 else 593 _GetDefaultEntrySize(entry.type, &frame.right, &frame.bottom); 594 595 // figure amount this entry will displace: 596 float fDisplacement = frame.Width() + entry.fPadding + 1; 597 598 // set entry's top-left position: 599 if (!fLayoutSet.size()) { 600 // sole entry: 601 if (fAlignMode == ALIGN_FLUSH_RIGHT) 602 frame.OffsetBy(Bounds().right - frame.Width(), 0.0); 603 } else if (index) { 604 // insert at right side 605 if (fAlignMode == ALIGN_FLUSH_LEFT) 606 frame.OffsetBy(fLayoutSet.back().frame.right + 1 + entry.fPadding, 0.0); 607 else 608 frame.OffsetBy(fLayoutSet.back().frame.right - frame.Width(), 0.0); //+++++ 609 } else { 610 // insert at left side 611 if (fAlignMode == ALIGN_FLUSH_RIGHT) 612 frame.OffsetBy(fLayoutSet.front().frame.left - fDisplacement, 0.0); 613 } 614 615 // add to layout set 616 entry.frame = frame; 617 fLayoutSet.insert( 618 index ? fLayoutSet.end() : fLayoutSet.begin(), 619 entry); 620 621 // slide following or preceding entries (depending on align mode) 622 // to make room: 623 switch (fAlignMode) { 624 case ALIGN_FLUSH_LEFT: 625 // following entries are shifted to the right 626 for(uint32 n = index+1; n < fLayoutSet.size(); n++) 627 _SlideEntry(n, fDisplacement); 628 break; 629 630 case ALIGN_FLUSH_RIGHT: { 631 // preceding entries are shifted to the left 632 for(int n = index-1; n >= 0; n--) 633 _SlideEntry(n, -fDisplacement); 634 635 break; 636 } 637 } 638 // 639 // PRINT(( 640 // "### added entry: (%.1f,%.1f)-(%.1f,%.1f)\n", 641 // frame.left, frame.top, frame.right, frame.bottom)); 642 } 643 644 645 void 646 ValControl::_SlideEntry(int index, float delta) 647 { 648 ValCtrlLayoutEntry& e = fLayoutSet[index]; 649 e.frame.OffsetBy(delta, 0.0); 650 651 // move & possibly resize view: 652 if (e.pView) { 653 e.pView->MoveTo(e.frame.LeftTop()); 654 655 BRect curFrame = e.pView->Frame(); 656 float fWidth = e.frame.Width(); 657 float fHeight = e.frame.Height(); 658 if (curFrame.Width() != fWidth 659 || curFrame.Height() != fHeight) 660 e.pView->ResizeTo(fWidth + 5.0, fHeight); 661 } 662 } 663 664 665 uint16 666 ValControl::_LocationToIndex(entry_location from, uint16 distance) const 667 { 668 uint16 nResult = 0; 669 670 switch (from) { 671 case FROM_LEFT: 672 nResult = distance; 673 break; 674 675 case FROM_RIGHT: 676 nResult = fLayoutSet.size() - distance; 677 break; 678 } 679 680 ASSERT(nResult <= fLayoutSet.size()); 681 return nResult; 682 } 683 684 685 void 686 ValControl::_GetDefaultEntrySize(ValCtrlLayoutEntry::entry_type type, 687 float* outWidth, float* outHeight) 688 { 689 switch (type) { 690 case ValCtrlLayoutEntry::SEGMENT_ENTRY: 691 case ValCtrlLayoutEntry::VIEW_ENTRY: 692 *outWidth = 1.0; 693 *outHeight = 1.0; 694 break; 695 696 case ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY: 697 *outWidth = fDecimalPointWidth; 698 *outHeight = fDecimalPointHeight; 699 break; 700 } 701 } 702