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