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 23using 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 34BTitledColumn::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 47void 48BTitledColumn::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 58void 59BTitledColumn::GetColumnName(BString* into) const 60{ 61 *into = fTitle; 62} 63 64 65void 66BTitledColumn::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 99void 100BTitledColumn::SetTitle(const char* title) 101{ 102 fTitle.SetTo(title); 103} 104 105 106void 107BTitledColumn::Title(BString* forTitle) const 108{ 109 if (forTitle) 110 forTitle->SetTo(fTitle.String()); 111} 112 113 114float 115BTitledColumn::FontHeight() const 116{ 117 return fFontHeight; 118} 119 120 121float 122BTitledColumn::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 131BStringField::BStringField(const char* string) 132 : 133 fWidth(0), 134 fString(string), 135 fClippedString(string) 136{ 137} 138 139 140void 141BStringField::SetString(const char* val) 142{ 143 fString = val; 144 fClippedString = ""; 145 fWidth = 0; 146} 147 148 149const char* 150BStringField::String() const 151{ 152 return fString.String(); 153} 154 155 156void 157BStringField::SetWidth(float width) 158{ 159 fWidth = width; 160} 161 162 163float 164BStringField::Width() 165{ 166 return fWidth; 167} 168 169 170void 171BStringField::SetClippedString(const char* val) 172{ 173 fClippedString = val; 174} 175 176 177bool 178BStringField::HasClippedString() const 179{ 180 return !fClippedString.IsEmpty(); 181} 182 183 184const char* 185BStringField::ClippedString() 186{ 187 return fClippedString.String(); 188} 189 190 191// #pragma mark - BStringColumn 192 193 194BStringColumn::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 203void 204BStringColumn::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 228float 229BStringColumn::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 236int 237BStringColumn::CompareFields(BField* field1, BField* field2) 238{ 239 return ICompare(((BStringField*)field1)->String(), 240 (((BStringField*)field2)->String())); 241} 242 243 244bool 245BStringColumn::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 254BDateField::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 266void 267BDateField::SetWidth(float width) 268{ 269 fWidth = width; 270} 271 272 273float 274BDateField::Width() 275{ 276 return fWidth; 277} 278 279 280void 281BDateField::SetClippedString(const char* string) 282{ 283 fClippedString = string; 284} 285 286 287const char* 288BDateField::ClippedString() 289{ 290 return fClippedString.String(); 291} 292 293 294time_t 295BDateField::Seconds() 296{ 297 return fSeconds; 298} 299 300 301time_t 302BDateField::UnixTime() 303{ 304 return fUnixTime; 305} 306 307 308// #pragma mark - BDateColumn 309 310 311BDateColumn::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 320void 321BDateColumn::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 371int 372BDateColumn::CompareFields(BField* field1, BField* field2) 373{ 374 return((BDateField*)field1)->Seconds() - ((BDateField*)field2)->Seconds(); 375} 376 377 378// #pragma mark - BSizeField 379 380 381BSizeField::BSizeField(off_t size) 382 : 383 fSize(size) 384{ 385} 386 387 388void 389BSizeField::SetSize(off_t size) 390{ 391 fSize = size; 392} 393 394 395off_t 396BSizeField::Size() 397{ 398 return fSize; 399} 400 401 402// #pragma mark - BSizeColumn 403 404 405BSizeColumn::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 417void 418BSizeColumn::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 480int 481BSizeColumn::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 495BIntegerField::BIntegerField(int32 number) 496 : 497 fInteger(number) 498{ 499} 500 501 502void 503BIntegerField::SetValue(int32 value) 504{ 505 fInteger = value; 506} 507 508 509int32 510BIntegerField::Value() 511{ 512 return fInteger; 513} 514 515 516// #pragma mark - BIntegerColumn 517 518 519BIntegerColumn::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 527void 528BIntegerColumn::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 539int 540BIntegerColumn::CompareFields(BField *field1, BField *field2) 541{ 542 return (((BIntegerField*)field1)->Value() - ((BIntegerField*)field2)->Value()); 543} 544 545 546// #pragma mark - GraphColumn 547 548 549GraphColumn::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 557void 558GraphColumn::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 595BBitmapField::BBitmapField(BBitmap* bitmap) 596 : 597 fBitmap(bitmap) 598{ 599} 600 601 602const BBitmap* 603BBitmapField::Bitmap() 604{ 605 return fBitmap; 606} 607 608 609void 610BBitmapField::SetBitmap(BBitmap* bitmap) 611{ 612 fBitmap = bitmap; 613} 614 615 616// #pragma mark - BBitmapColumn 617 618 619BBitmapColumn::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 627void 628BBitmapColumn::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 670int 671BBitmapColumn::CompareFields(BField* /*field1*/, BField* /*field2*/) 672{ 673 // Comparing bitmaps doesn't really make sense... 674 return 0; 675} 676 677 678bool 679BBitmapColumn::AcceptsField(const BField *field) const 680{ 681 return static_cast<bool>(dynamic_cast<const BBitmapField*>(field)); 682} 683