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