1 /******************************************************************************* 2 / 3 / File: ColumnTypes.h 4 / 5 / Description: Experimental classes that implement particular column/field 6 / data types for use in BColumnListView. 7 / 8 / Copyright 2000+, Be Incorporated, All Rights Reserved 9 / Copyright 2024, Haiku, Inc. All Rights Reserved 10 / 11 *******************************************************************************/ 12 13 14 #include "ColumnTypes.h" 15 16 #include <StringFormat.h> 17 #include <SystemCatalog.h> 18 #include <View.h> 19 20 #include <stdio.h> 21 22 23 using BPrivate::gSystemCatalog; 24 25 #undef B_TRANSLATE_COMMENT 26 #define B_TRANSLATE_COMMENT(str, comment) \ 27 gSystemCatalog.GetString(B_TRANSLATE_MARK_COMMENT(str, comment), \ 28 B_TRANSLATION_CONTEXT, (comment)) 29 30 31 #define kTEXT_MARGIN 8 32 33 34 BTitledColumn::BTitledColumn(const char* title, float width, float minWidth, 35 float maxWidth, alignment align) 36 : 37 BColumn(width, minWidth, maxWidth, align), 38 fTitle(title) 39 { 40 font_height fh; 41 42 be_plain_font->GetHeight(&fh); 43 fFontHeight = fh.descent + fh.leading; 44 } 45 46 47 void 48 BTitledColumn::DrawTitle(BRect rect, BView* parent) 49 { 50 float width = rect.Width() - (2 * kTEXT_MARGIN); 51 BString out_string(fTitle); 52 53 parent->TruncateString(&out_string, B_TRUNCATE_END, width + 2); 54 DrawString(out_string.String(), parent, rect); 55 } 56 57 58 void 59 BTitledColumn::GetColumnName(BString* into) const 60 { 61 *into = fTitle; 62 } 63 64 65 void 66 BTitledColumn::DrawString(const char* string, BView* parent, BRect rect) 67 { 68 float width = rect.Width() - (2 * kTEXT_MARGIN); 69 float y; 70 BFont font; 71 font_height finfo; 72 73 parent->GetFont(&font); 74 font.GetHeight(&finfo); 75 y = rect.top + finfo.ascent 76 + (rect.Height() - ceilf(finfo.ascent + finfo.descent)) / 2.0f; 77 78 switch (Alignment()) { 79 default: 80 case B_ALIGN_LEFT: 81 parent->MovePenTo(rect.left + kTEXT_MARGIN, y); 82 break; 83 84 case B_ALIGN_CENTER: 85 parent->MovePenTo(rect.left + kTEXT_MARGIN 86 + ((width - font.StringWidth(string)) / 2), y); 87 break; 88 89 case B_ALIGN_RIGHT: 90 parent->MovePenTo(rect.right - kTEXT_MARGIN 91 - font.StringWidth(string), y); 92 break; 93 } 94 95 parent->DrawString(string); 96 } 97 98 99 void 100 BTitledColumn::SetTitle(const char* title) 101 { 102 fTitle.SetTo(title); 103 } 104 105 106 void 107 BTitledColumn::Title(BString* forTitle) const 108 { 109 if (forTitle) 110 forTitle->SetTo(fTitle.String()); 111 } 112 113 114 float 115 BTitledColumn::FontHeight() const 116 { 117 return fFontHeight; 118 } 119 120 121 float 122 BTitledColumn::GetPreferredWidth(BField *_field, BView* parent) const 123 { 124 return parent->StringWidth(fTitle.String()) + 2 * kTEXT_MARGIN; 125 } 126 127 128 // #pragma mark - BStringField 129 130 131 BStringField::BStringField(const char* string) 132 : 133 fWidth(0), 134 fString(string), 135 fClippedString(string) 136 { 137 } 138 139 140 void 141 BStringField::SetString(const char* val) 142 { 143 fString = val; 144 fClippedString = ""; 145 fWidth = 0; 146 } 147 148 149 const char* 150 BStringField::String() const 151 { 152 return fString.String(); 153 } 154 155 156 void 157 BStringField::SetWidth(float width) 158 { 159 fWidth = width; 160 } 161 162 163 float 164 BStringField::Width() 165 { 166 return fWidth; 167 } 168 169 170 void 171 BStringField::SetClippedString(const char* val) 172 { 173 fClippedString = val; 174 } 175 176 177 bool 178 BStringField::HasClippedString() const 179 { 180 return !fClippedString.IsEmpty(); 181 } 182 183 184 const char* 185 BStringField::ClippedString() 186 { 187 return fClippedString.String(); 188 } 189 190 191 // #pragma mark - BStringColumn 192 193 194 BStringColumn::BStringColumn(const char* title, float width, float minWidth, 195 float maxWidth, uint32 truncate, alignment align) 196 : 197 BTitledColumn(title, width, minWidth, maxWidth, align), 198 fTruncate(truncate) 199 { 200 } 201 202 203 void 204 BStringColumn::DrawField(BField* _field, BRect rect, BView* parent) 205 { 206 float width = rect.Width() - (2 * kTEXT_MARGIN); 207 BStringField* field = static_cast<BStringField*>(_field); 208 float fieldWidth = field->Width(); 209 bool updateNeeded = width != fieldWidth; 210 211 if (updateNeeded) { 212 BString out_string(field->String()); 213 float preferredWidth = parent->StringWidth(out_string.String()); 214 if (width < preferredWidth) { 215 parent->TruncateString(&out_string, fTruncate, width + 2); 216 field->SetClippedString(out_string.String()); 217 } else 218 field->SetClippedString(""); 219 field->SetWidth(width); 220 } 221 222 DrawString(field->HasClippedString() 223 ? field->ClippedString() 224 : field->String(), parent, rect); 225 } 226 227 228 float 229 BStringColumn::GetPreferredWidth(BField *_field, BView* parent) const 230 { 231 BStringField* field = static_cast<BStringField*>(_field); 232 return parent->StringWidth(field->String()) + 2 * kTEXT_MARGIN; 233 } 234 235 236 int 237 BStringColumn::CompareFields(BField* field1, BField* field2) 238 { 239 return ICompare(((BStringField*)field1)->String(), 240 (((BStringField*)field2)->String())); 241 } 242 243 244 bool 245 BStringColumn::AcceptsField(const BField *field) const 246 { 247 return static_cast<bool>(dynamic_cast<const BStringField*>(field)); 248 } 249 250 251 // #pragma mark - BDateField 252 253 254 BDateField::BDateField(time_t* time) 255 : 256 fTime(*localtime(time)), 257 fUnixTime(*time), 258 fSeconds(0), 259 fClippedString(""), 260 fWidth(0) 261 { 262 fSeconds = mktime(&fTime); 263 } 264 265 266 void 267 BDateField::SetWidth(float width) 268 { 269 fWidth = width; 270 } 271 272 273 float 274 BDateField::Width() 275 { 276 return fWidth; 277 } 278 279 280 void 281 BDateField::SetClippedString(const char* string) 282 { 283 fClippedString = string; 284 } 285 286 287 const char* 288 BDateField::ClippedString() 289 { 290 return fClippedString.String(); 291 } 292 293 294 time_t 295 BDateField::Seconds() 296 { 297 return fSeconds; 298 } 299 300 301 time_t 302 BDateField::UnixTime() 303 { 304 return fUnixTime; 305 } 306 307 308 // #pragma mark - BDateColumn 309 310 311 BDateColumn::BDateColumn(const char* title, float width, float minWidth, 312 float maxWidth, alignment align) 313 : 314 BTitledColumn(title, width, minWidth, maxWidth, align), 315 fTitle(title) 316 { 317 } 318 319 320 void 321 BDateColumn::DrawField(BField* _field, BRect rect, BView* parent) 322 { 323 float width = rect.Width() - (2 * kTEXT_MARGIN); 324 BDateField* field = (BDateField*)_field; 325 326 if (field->Width() != rect.Width()) { 327 char dateString[256]; 328 time_t currentTime = field->UnixTime(); 329 tm time_data; 330 BFont font; 331 332 parent->GetFont(&font); 333 localtime_r(¤tTime, &time_data); 334 335 // dateStyles[] and timeStyles[] must be the same length 336 const BDateFormatStyle dateStyles[] = { 337 B_FULL_DATE_FORMAT, B_FULL_DATE_FORMAT, B_LONG_DATE_FORMAT, B_LONG_DATE_FORMAT, 338 B_MEDIUM_DATE_FORMAT, B_SHORT_DATE_FORMAT, 339 }; 340 341 const BTimeFormatStyle timeStyles[] = { 342 B_MEDIUM_TIME_FORMAT, B_SHORT_TIME_FORMAT, B_MEDIUM_TIME_FORMAT, B_SHORT_TIME_FORMAT, 343 B_SHORT_TIME_FORMAT, B_SHORT_TIME_FORMAT, 344 }; 345 346 size_t index; 347 for (index = 0; index < B_COUNT_OF(dateStyles); index++) { 348 ssize_t output = fDateTimeFormat.Format(dateString, sizeof(dateString), currentTime, 349 dateStyles[index], timeStyles[index]); 350 if (output >= 0 && font.StringWidth(dateString) <= width) 351 break; 352 } 353 354 if (index == B_COUNT_OF(dateStyles)) 355 fDateFormat.Format(dateString, sizeof(dateString), currentTime, B_SHORT_DATE_FORMAT); 356 357 if (font.StringWidth(dateString) > width) { 358 BString out_string(dateString); 359 360 parent->TruncateString(&out_string, B_TRUNCATE_MIDDLE, width + 2); 361 strcpy(dateString, out_string.String()); 362 } 363 field->SetClippedString(dateString); 364 field->SetWidth(width); 365 } 366 367 DrawString(field->ClippedString(), parent, rect); 368 } 369 370 371 int 372 BDateColumn::CompareFields(BField* field1, BField* field2) 373 { 374 return((BDateField*)field1)->Seconds() - ((BDateField*)field2)->Seconds(); 375 } 376 377 378 // #pragma mark - BSizeField 379 380 381 BSizeField::BSizeField(off_t size) 382 : 383 fSize(size) 384 { 385 } 386 387 388 void 389 BSizeField::SetSize(off_t size) 390 { 391 fSize = size; 392 } 393 394 395 off_t 396 BSizeField::Size() 397 { 398 return fSize; 399 } 400 401 402 // #pragma mark - BSizeColumn 403 404 405 BSizeColumn::BSizeColumn(const char* title, float width, float minWidth, 406 float maxWidth, alignment align) 407 : 408 BTitledColumn(title, width, minWidth, maxWidth, align) 409 { 410 } 411 412 413 #undef B_TRANSLATION_CONTEXT 414 #define B_TRANSLATION_CONTEXT "StringForSize" 415 416 417 void 418 BSizeColumn::DrawField(BField* _field, BRect rect, BView* parent) 419 { 420 BFont font; 421 BString printedSize; 422 BString string; 423 424 float width = rect.Width() - (2 * kTEXT_MARGIN); 425 426 double value = ((BSizeField*)_field)->Size(); 427 parent->GetFont(&font); 428 429 // we cannot use string_for_size due to the precision/cell width logic 430 const char* kFormats[] = { 431 B_TRANSLATE_MARK_COMMENT("{0, plural, one{%s byte} other{%s bytes}}", "size unit"), 432 B_TRANSLATE_MARK_COMMENT("%s KiB", "size unit"), 433 B_TRANSLATE_MARK_COMMENT("%s MiB", "size unit"), 434 B_TRANSLATE_MARK_COMMENT("%s GiB", "size unit"), 435 B_TRANSLATE_MARK_COMMENT("%s TiB", "size unit") 436 }; 437 438 size_t index = 0; 439 while (index < B_COUNT_OF(kFormats) - 1 && value >= 1024.0) { 440 value /= 1024.0; 441 index++; 442 } 443 444 BString format; 445 BStringFormat formatter( 446 gSystemCatalog.GetString(kFormats[index], B_TRANSLATION_CONTEXT, "size unit")); 447 formatter.Format(format, value); 448 449 if (index == 0) { 450 fNumberFormat.SetPrecision(0); 451 fNumberFormat.Format(printedSize, value); 452 string.SetToFormat(format.String(), printedSize.String()); 453 454 if (font.StringWidth(string) > width) { 455 BStringFormat formatter(B_TRANSLATE_COMMENT("%s B", "size unit, narrow space")); 456 format.Truncate(0); 457 formatter.Format(format, value); 458 string.SetToFormat(format.String(), printedSize.String()); 459 } 460 } else { 461 int precision = 2; 462 while (precision >= 0) { 463 fNumberFormat.SetPrecision(precision); 464 fNumberFormat.Format(printedSize, value); 465 string.SetToFormat(format.String(), printedSize.String()); 466 if (font.StringWidth(string) <= width) 467 break; 468 469 precision--; 470 } 471 } 472 473 parent->TruncateString(&string, B_TRUNCATE_MIDDLE, width + 2); 474 DrawString(string.String(), parent, rect); 475 } 476 477 #undef B_TRANSLATION_CONTEXT 478 479 480 int 481 BSizeColumn::CompareFields(BField* field1, BField* field2) 482 { 483 off_t diff = ((BSizeField*)field1)->Size() - ((BSizeField*)field2)->Size(); 484 if (diff > 0) 485 return 1; 486 if (diff < 0) 487 return -1; 488 return 0; 489 } 490 491 492 // #pragma mark - BIntegerField 493 494 495 BIntegerField::BIntegerField(int32 number) 496 : 497 fInteger(number) 498 { 499 } 500 501 502 void 503 BIntegerField::SetValue(int32 value) 504 { 505 fInteger = value; 506 } 507 508 509 int32 510 BIntegerField::Value() 511 { 512 return fInteger; 513 } 514 515 516 // #pragma mark - BIntegerColumn 517 518 519 BIntegerColumn::BIntegerColumn(const char* title, float width, float minWidth, 520 float maxWidth, alignment align) 521 : 522 BTitledColumn(title, width, minWidth, maxWidth, align) 523 { 524 } 525 526 527 void 528 BIntegerColumn::DrawField(BField *field, BRect rect, BView* parent) 529 { 530 BString string; 531 532 fNumberFormat.Format(string, (int32)((BIntegerField*)field)->Value()); 533 float width = rect.Width() - (2 * kTEXT_MARGIN); 534 parent->TruncateString(&string, B_TRUNCATE_MIDDLE, width + 2); 535 DrawString(string.String(), parent, rect); 536 } 537 538 539 int 540 BIntegerColumn::CompareFields(BField *field1, BField *field2) 541 { 542 return (((BIntegerField*)field1)->Value() - ((BIntegerField*)field2)->Value()); 543 } 544 545 546 // #pragma mark - GraphColumn 547 548 549 GraphColumn::GraphColumn(const char* name, float width, float minWidth, 550 float maxWidth, alignment align) 551 : 552 BIntegerColumn(name, width, minWidth, maxWidth, align) 553 { 554 } 555 556 557 void 558 GraphColumn::DrawField(BField* field, BRect rect, BView* parent) 559 { 560 double fieldValue = ((BIntegerField*)field)->Value(); 561 double percentValue = fieldValue / 100.0; 562 563 if (percentValue > 1.0) 564 percentValue = 1.0; 565 else if (percentValue < 0.0) 566 percentValue = 0.0; 567 568 BRect graphRect(rect); 569 graphRect.InsetBy(5, 3); 570 parent->StrokeRoundRect(graphRect, 2.5, 2.5); 571 572 if (percentValue > 0.0) { 573 graphRect.InsetBy(1, 1); 574 double value = graphRect.Width() * percentValue; 575 graphRect.right = graphRect.left + value; 576 parent->SetHighUIColor(B_NAVIGATION_BASE_COLOR); 577 parent->FillRect(graphRect); 578 } 579 580 parent->SetDrawingMode(B_OP_INVERT); 581 parent->SetHighColor(128, 128, 128); 582 583 BString percentString; 584 fNumberFormat.FormatPercent(percentString, percentValue); 585 float width = be_plain_font->StringWidth(percentString); 586 587 parent->MovePenTo(rect.left + rect.Width() / 2 - width / 2, rect.bottom - FontHeight()); 588 parent->DrawString(percentString.String()); 589 } 590 591 592 // #pragma mark - BBitmapField 593 594 595 BBitmapField::BBitmapField(BBitmap* bitmap) 596 : 597 fBitmap(bitmap) 598 { 599 } 600 601 602 const BBitmap* 603 BBitmapField::Bitmap() 604 { 605 return fBitmap; 606 } 607 608 609 void 610 BBitmapField::SetBitmap(BBitmap* bitmap) 611 { 612 fBitmap = bitmap; 613 } 614 615 616 // #pragma mark - BBitmapColumn 617 618 619 BBitmapColumn::BBitmapColumn(const char* title, float width, float minWidth, 620 float maxWidth, alignment align) 621 : 622 BTitledColumn(title, width, minWidth, maxWidth, align) 623 { 624 } 625 626 627 void 628 BBitmapColumn::DrawField(BField* field, BRect rect, BView* parent) 629 { 630 BBitmapField* bitmapField = static_cast<BBitmapField*>(field); 631 const BBitmap* bitmap = bitmapField->Bitmap(); 632 633 if (bitmap != NULL) { 634 float x = 0.0; 635 BRect r = bitmap->Bounds(); 636 float y = rect.top + ((rect.Height() - r.Height()) / 2); 637 638 switch (Alignment()) { 639 default: 640 case B_ALIGN_LEFT: 641 x = rect.left + kTEXT_MARGIN; 642 break; 643 644 case B_ALIGN_CENTER: 645 x = rect.left + ((rect.Width() - r.Width()) / 2); 646 break; 647 648 case B_ALIGN_RIGHT: 649 x = rect.right - kTEXT_MARGIN - r.Width(); 650 break; 651 } 652 // setup drawing mode according to bitmap color space, 653 // restore previous mode after drawing 654 drawing_mode oldMode = parent->DrawingMode(); 655 if (bitmap->ColorSpace() == B_RGBA32 656 || bitmap->ColorSpace() == B_RGBA32_BIG) { 657 parent->SetDrawingMode(B_OP_ALPHA); 658 parent->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 659 } else { 660 parent->SetDrawingMode(B_OP_OVER); 661 } 662 663 parent->DrawBitmap(bitmap, BPoint(x, y)); 664 665 parent->SetDrawingMode(oldMode); 666 } 667 } 668 669 670 int 671 BBitmapColumn::CompareFields(BField* /*field1*/, BField* /*field2*/) 672 { 673 // Comparing bitmaps doesn't really make sense... 674 return 0; 675 } 676 677 678 bool 679 BBitmapColumn::AcceptsField(const BField *field) const 680 { 681 return static_cast<bool>(dynamic_cast<const BBitmapField*>(field)); 682 } 683