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