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