1 /* 2 * Copyright 2001-2015, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 * Axel Dörfler, axeld@pinc-software.de 8 * Frans van Nispen (xlr8@tref.nl) 9 * Ingo Weinhold <ingo_weinhold@gmx.de> 10 */ 11 12 13 //! BStringView draws a non-editable text string. 14 15 16 #include <StringView.h> 17 18 #include <stdio.h> 19 #include <stdlib.h> 20 #include <string.h> 21 22 #include <LayoutUtils.h> 23 #include <Message.h> 24 #include <PropertyInfo.h> 25 #include <StringList.h> 26 #include <View.h> 27 #include <Window.h> 28 29 #include <binary_compatibility/Interface.h> 30 31 32 static property_info sPropertyList[] = { 33 { 34 "Text", 35 { B_GET_PROPERTY, B_SET_PROPERTY }, 36 { B_DIRECT_SPECIFIER }, 37 NULL, 0, 38 { B_STRING_TYPE } 39 }, 40 { 41 "Alignment", 42 { B_GET_PROPERTY, B_SET_PROPERTY }, 43 { B_DIRECT_SPECIFIER }, 44 NULL, 0, 45 { B_INT32_TYPE } 46 }, 47 48 { 0 } 49 }; 50 51 52 BStringView::BStringView(BRect frame, const char* name, const char* text, 53 uint32 resizingMode, uint32 flags) 54 : 55 BView(frame, name, resizingMode, flags | B_FULL_UPDATE_ON_RESIZE), 56 fText(text ? strdup(text) : NULL), 57 fTruncation(B_NO_TRUNCATION), 58 fAlign(B_ALIGN_LEFT), 59 fPreferredSize(text ? _StringWidth(text) : 0.0, -1) 60 { 61 } 62 63 64 BStringView::BStringView(const char* name, const char* text, uint32 flags) 65 : 66 BView(name, flags | B_FULL_UPDATE_ON_RESIZE), 67 fText(text ? strdup(text) : NULL), 68 fTruncation(B_NO_TRUNCATION), 69 fAlign(B_ALIGN_LEFT), 70 fPreferredSize(text ? _StringWidth(text) : 0.0, -1) 71 { 72 } 73 74 75 BStringView::BStringView(BMessage* archive) 76 : 77 BView(archive), 78 fText(NULL), 79 fTruncation(B_NO_TRUNCATION), 80 fPreferredSize(0, -1) 81 { 82 fAlign = (alignment)archive->GetInt32("_align", B_ALIGN_LEFT); 83 fTruncation = (uint32)archive->GetInt32("_truncation", B_NO_TRUNCATION); 84 85 const char* text = archive->GetString("_text", NULL); 86 87 SetText(text); 88 SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE); 89 } 90 91 92 BStringView::~BStringView() 93 { 94 free(fText); 95 } 96 97 98 // #pragma mark - Archiving methods 99 100 101 BArchivable* 102 BStringView::Instantiate(BMessage* data) 103 { 104 if (!validate_instantiation(data, "BStringView")) 105 return NULL; 106 107 return new BStringView(data); 108 } 109 110 111 status_t 112 BStringView::Archive(BMessage* data, bool deep) const 113 { 114 status_t status = BView::Archive(data, deep); 115 116 if (status == B_OK && fText) 117 status = data->AddString("_text", fText); 118 if (status == B_OK && fTruncation != B_NO_TRUNCATION) 119 status = data->AddInt32("_truncation", fTruncation); 120 if (status == B_OK) 121 status = data->AddInt32("_align", fAlign); 122 123 return status; 124 } 125 126 127 // #pragma mark - Hook methods 128 129 130 void 131 BStringView::AttachedToWindow() 132 { 133 if (HasDefaultColors()) 134 SetHighUIColor(B_PANEL_TEXT_COLOR); 135 136 BView* parent = Parent(); 137 138 if (parent != NULL) { 139 float tint = B_NO_TINT; 140 color_which which = parent->ViewUIColor(&tint); 141 142 if (which != B_NO_COLOR) { 143 SetViewUIColor(which, tint); 144 SetLowUIColor(which, tint); 145 } else { 146 SetViewColor(parent->ViewColor()); 147 SetLowColor(ViewColor()); 148 } 149 } 150 151 if (ViewColor() == B_TRANSPARENT_COLOR) 152 AdoptSystemColors(); 153 } 154 155 156 void 157 BStringView::DetachedFromWindow() 158 { 159 BView::DetachedFromWindow(); 160 } 161 162 163 void 164 BStringView::AllAttached() 165 { 166 BView::AllAttached(); 167 } 168 169 170 void 171 BStringView::AllDetached() 172 { 173 BView::AllDetached(); 174 } 175 176 177 // #pragma mark - Layout methods 178 179 180 void 181 BStringView::MakeFocus(bool focus) 182 { 183 BView::MakeFocus(focus); 184 } 185 186 187 void 188 BStringView::GetPreferredSize(float* _width, float* _height) 189 { 190 _ValidatePreferredSize(); 191 192 if (_width) 193 *_width = fPreferredSize.width; 194 195 if (_height) 196 *_height = fPreferredSize.height; 197 } 198 199 200 BSize 201 BStringView::MinSize() 202 { 203 return BLayoutUtils::ComposeSize(ExplicitMinSize(), 204 _ValidatePreferredSize()); 205 } 206 207 208 BSize 209 BStringView::MaxSize() 210 { 211 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 212 _ValidatePreferredSize()); 213 } 214 215 216 BSize 217 BStringView::PreferredSize() 218 { 219 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), 220 _ValidatePreferredSize()); 221 } 222 223 224 void 225 BStringView::ResizeToPreferred() 226 { 227 float width, height; 228 GetPreferredSize(&width, &height); 229 230 // Resize the width only for B_ALIGN_LEFT (if its large enough already, that is) 231 if (Bounds().Width() > width && Alignment() != B_ALIGN_LEFT) 232 width = Bounds().Width(); 233 234 BView::ResizeTo(width, height); 235 } 236 237 238 BAlignment 239 BStringView::LayoutAlignment() 240 { 241 return BLayoutUtils::ComposeAlignment(ExplicitAlignment(), 242 BAlignment(fAlign, B_ALIGN_MIDDLE)); 243 } 244 245 246 // #pragma mark - More hook methods 247 248 249 void 250 BStringView::FrameMoved(BPoint newPosition) 251 { 252 BView::FrameMoved(newPosition); 253 } 254 255 256 void 257 BStringView::FrameResized(float newWidth, float newHeight) 258 { 259 BView::FrameResized(newWidth, newHeight); 260 } 261 262 263 void 264 BStringView::Draw(BRect updateRect) 265 { 266 if (!fText) 267 return; 268 269 if (LowUIColor() == B_NO_COLOR) 270 SetLowColor(ViewColor()); 271 272 font_height fontHeight; 273 GetFontHeight(&fontHeight); 274 275 BRect bounds = Bounds(); 276 277 BStringList lines; 278 BString(fText).Split("\n", false, lines); 279 for (int i = 0; i < lines.CountStrings(); i++) { 280 const char* text = lines.StringAt(i).String(); 281 float width = StringWidth(text); 282 BString truncated; 283 if (fTruncation != B_NO_TRUNCATION && width > bounds.Width()) { 284 // The string needs to be truncated 285 // TODO: we should cache this 286 truncated = lines.StringAt(i); 287 TruncateString(&truncated, fTruncation, bounds.Width()); 288 text = truncated.String(); 289 width = StringWidth(text); 290 } 291 292 float y = (bounds.top + bounds.bottom - ceilf(fontHeight.descent)) 293 - ceilf(fontHeight.ascent + fontHeight.descent + fontHeight.leading) 294 * (lines.CountStrings() - i - 1); 295 float x; 296 switch (fAlign) { 297 case B_ALIGN_RIGHT: 298 x = bounds.Width() - width; 299 break; 300 301 case B_ALIGN_CENTER: 302 x = (bounds.Width() - width) / 2.0; 303 break; 304 305 default: 306 x = 0.0; 307 break; 308 } 309 310 DrawString(text, BPoint(x, y)); 311 } 312 } 313 314 315 void 316 BStringView::MessageReceived(BMessage* message) 317 { 318 if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) { 319 int32 index; 320 BMessage specifier; 321 int32 form; 322 const char* property; 323 if (message->GetCurrentSpecifier(&index, &specifier, &form, &property) 324 != B_OK) { 325 BView::MessageReceived(message); 326 return; 327 } 328 329 BMessage reply(B_REPLY); 330 bool handled = false; 331 if (strcmp(property, "Text") == 0) { 332 if (message->what == B_GET_PROPERTY) { 333 reply.AddString("result", fText); 334 handled = true; 335 } else { 336 const char* text; 337 if (message->FindString("data", &text) == B_OK) { 338 SetText(text); 339 reply.AddInt32("error", B_OK); 340 handled = true; 341 } 342 } 343 } else if (strcmp(property, "Alignment") == 0) { 344 if (message->what == B_GET_PROPERTY) { 345 reply.AddInt32("result", (int32)fAlign); 346 handled = true; 347 } else { 348 int32 align; 349 if (message->FindInt32("data", &align) == B_OK) { 350 SetAlignment((alignment)align); 351 reply.AddInt32("error", B_OK); 352 handled = true; 353 } 354 } 355 } 356 357 if (handled) { 358 message->SendReply(&reply); 359 return; 360 } 361 } 362 363 BView::MessageReceived(message); 364 } 365 366 367 void 368 BStringView::MouseDown(BPoint point) 369 { 370 BView::MouseDown(point); 371 } 372 373 374 void 375 BStringView::MouseUp(BPoint point) 376 { 377 BView::MouseUp(point); 378 } 379 380 381 void 382 BStringView::MouseMoved(BPoint point, uint32 transit, const BMessage* msg) 383 { 384 BView::MouseMoved(point, transit, msg); 385 } 386 387 388 // #pragma mark - 389 390 391 void 392 BStringView::SetText(const char* text) 393 { 394 if ((text && fText && !strcmp(text, fText)) || (!text && !fText)) 395 return; 396 397 free(fText); 398 fText = text ? strdup(text) : NULL; 399 400 float newStringWidth = _StringWidth(fText); 401 if (fPreferredSize.width != newStringWidth) { 402 fPreferredSize.width = newStringWidth; 403 InvalidateLayout(); 404 } 405 406 Invalidate(); 407 } 408 409 410 const char* 411 BStringView::Text() const 412 { 413 return fText; 414 } 415 416 417 void 418 BStringView::SetAlignment(alignment flag) 419 { 420 fAlign = flag; 421 Invalidate(); 422 } 423 424 425 alignment 426 BStringView::Alignment() const 427 { 428 return fAlign; 429 } 430 431 432 void 433 BStringView::SetTruncation(uint32 truncationMode) 434 { 435 if (fTruncation != truncationMode) { 436 fTruncation = truncationMode; 437 Invalidate(); 438 } 439 } 440 441 442 uint32 443 BStringView::Truncation() const 444 { 445 return fTruncation; 446 } 447 448 449 BHandler* 450 BStringView::ResolveSpecifier(BMessage* message, int32 index, 451 BMessage* specifier, int32 form, const char* property) 452 { 453 BPropertyInfo propInfo(sPropertyList); 454 if (propInfo.FindMatch(message, 0, specifier, form, property) >= B_OK) 455 return this; 456 457 return BView::ResolveSpecifier(message, index, specifier, form, property); 458 } 459 460 461 status_t 462 BStringView::GetSupportedSuites(BMessage* data) 463 { 464 if (data == NULL) 465 return B_BAD_VALUE; 466 467 status_t status = data->AddString("suites", "suite/vnd.Be-string-view"); 468 if (status != B_OK) 469 return status; 470 471 BPropertyInfo propertyInfo(sPropertyList); 472 status = data->AddFlat("messages", &propertyInfo); 473 if (status != B_OK) 474 return status; 475 476 return BView::GetSupportedSuites(data); 477 } 478 479 480 void 481 BStringView::SetFont(const BFont* font, uint32 mask) 482 { 483 BView::SetFont(font, mask); 484 485 fPreferredSize.width = _StringWidth(fText); 486 487 Invalidate(); 488 InvalidateLayout(); 489 } 490 491 492 void 493 BStringView::LayoutInvalidated(bool descendants) 494 { 495 // invalidate cached preferred size 496 fPreferredSize.height = -1; 497 } 498 499 500 // #pragma mark - Perform 501 502 503 status_t 504 BStringView::Perform(perform_code code, void* _data) 505 { 506 switch (code) { 507 case PERFORM_CODE_MIN_SIZE: 508 ((perform_data_min_size*)_data)->return_value 509 = BStringView::MinSize(); 510 return B_OK; 511 512 case PERFORM_CODE_MAX_SIZE: 513 ((perform_data_max_size*)_data)->return_value 514 = BStringView::MaxSize(); 515 return B_OK; 516 517 case PERFORM_CODE_PREFERRED_SIZE: 518 ((perform_data_preferred_size*)_data)->return_value 519 = BStringView::PreferredSize(); 520 return B_OK; 521 522 case PERFORM_CODE_LAYOUT_ALIGNMENT: 523 ((perform_data_layout_alignment*)_data)->return_value 524 = BStringView::LayoutAlignment(); 525 return B_OK; 526 527 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 528 ((perform_data_has_height_for_width*)_data)->return_value 529 = BStringView::HasHeightForWidth(); 530 return B_OK; 531 532 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 533 { 534 perform_data_get_height_for_width* data 535 = (perform_data_get_height_for_width*)_data; 536 BStringView::GetHeightForWidth(data->width, &data->min, &data->max, 537 &data->preferred); 538 return B_OK; 539 } 540 541 case PERFORM_CODE_SET_LAYOUT: 542 { 543 perform_data_set_layout* data = (perform_data_set_layout*)_data; 544 BStringView::SetLayout(data->layout); 545 return B_OK; 546 } 547 548 case PERFORM_CODE_LAYOUT_INVALIDATED: 549 { 550 perform_data_layout_invalidated* data 551 = (perform_data_layout_invalidated*)_data; 552 BStringView::LayoutInvalidated(data->descendants); 553 return B_OK; 554 } 555 556 case PERFORM_CODE_DO_LAYOUT: 557 { 558 BStringView::DoLayout(); 559 return B_OK; 560 } 561 } 562 563 return BView::Perform(code, _data); 564 } 565 566 567 // #pragma mark - FBC padding methods 568 569 570 void BStringView::_ReservedStringView1() {} 571 void BStringView::_ReservedStringView2() {} 572 void BStringView::_ReservedStringView3() {} 573 574 575 // #pragma mark - Private methods 576 577 578 BStringView& 579 BStringView::operator=(const BStringView&) 580 { 581 // Assignment not allowed (private) 582 return *this; 583 } 584 585 586 BSize 587 BStringView::_ValidatePreferredSize() 588 { 589 if (fPreferredSize.height < 0) { 590 // height 591 font_height fontHeight; 592 GetFontHeight(&fontHeight); 593 594 int32 lines = 1; 595 char* temp = fText ? strchr(fText, '\n') : NULL; 596 while (temp != NULL) { 597 temp = strchr(temp + 1, '\n'); 598 lines++; 599 }; 600 601 fPreferredSize.height = ceilf(fontHeight.ascent + fontHeight.descent 602 + fontHeight.leading) * lines; 603 604 ResetLayoutInvalidation(); 605 } 606 607 return fPreferredSize; 608 } 609 610 611 float 612 BStringView::_StringWidth(const char* text) 613 { 614 if(text == NULL) 615 return 0.0f; 616 617 float maxWidth = 0.0f; 618 BStringList lines; 619 BString(fText).Split("\n", false, lines); 620 for (int i = 0; i < lines.CountStrings(); i++) { 621 float width = StringWidth(lines.StringAt(i)); 622 if (maxWidth < width) 623 maxWidth = width; 624 } 625 return maxWidth; 626 } 627 628 629 extern "C" void 630 B_IF_GCC_2(InvalidateLayout__11BStringViewb, 631 _ZN11BStringView16InvalidateLayoutEb)(BView* view, bool descendants) 632 { 633 perform_data_layout_invalidated data; 634 data.descendants = descendants; 635 636 view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 637 } 638