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