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
BTitledColumn(const char * title,float width,float minWidth,float maxWidth,alignment align)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
DrawTitle(BRect rect,BView * parent)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
GetColumnName(BString * into) const59 BTitledColumn::GetColumnName(BString* into) const
60 {
61 *into = fTitle;
62 }
63
64
65 void
DrawString(const char * string,BView * parent,BRect rect)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
SetTitle(const char * title)100 BTitledColumn::SetTitle(const char* title)
101 {
102 fTitle.SetTo(title);
103 }
104
105
106 void
Title(BString * forTitle) const107 BTitledColumn::Title(BString* forTitle) const
108 {
109 if (forTitle)
110 forTitle->SetTo(fTitle.String());
111 }
112
113
114 float
FontHeight() const115 BTitledColumn::FontHeight() const
116 {
117 return fFontHeight;
118 }
119
120
121 float
GetPreferredWidth(BField * _field,BView * parent) const122 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
BStringField(const char * string)131 BStringField::BStringField(const char* string)
132 :
133 fWidth(0),
134 fString(string),
135 fClippedString(string)
136 {
137 }
138
139
140 void
SetString(const char * val)141 BStringField::SetString(const char* val)
142 {
143 fString = val;
144 fClippedString = "";
145 fWidth = 0;
146 }
147
148
149 const char*
String() const150 BStringField::String() const
151 {
152 return fString.String();
153 }
154
155
156 void
SetWidth(float width)157 BStringField::SetWidth(float width)
158 {
159 fWidth = width;
160 }
161
162
163 float
Width()164 BStringField::Width()
165 {
166 return fWidth;
167 }
168
169
170 void
SetClippedString(const char * val)171 BStringField::SetClippedString(const char* val)
172 {
173 fClippedString = val;
174 }
175
176
177 bool
HasClippedString() const178 BStringField::HasClippedString() const
179 {
180 return !fClippedString.IsEmpty();
181 }
182
183
184 const char*
ClippedString()185 BStringField::ClippedString()
186 {
187 return fClippedString.String();
188 }
189
190
191 // #pragma mark - BStringColumn
192
193
BStringColumn(const char * title,float width,float minWidth,float maxWidth,uint32 truncate,alignment align)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
DrawField(BField * _field,BRect rect,BView * parent)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
GetPreferredWidth(BField * _field,BView * parent) const229 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
CompareFields(BField * field1,BField * field2)237 BStringColumn::CompareFields(BField* field1, BField* field2)
238 {
239 return ICompare(((BStringField*)field1)->String(),
240 (((BStringField*)field2)->String()));
241 }
242
243
244 bool
AcceptsField(const BField * field) const245 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
BDateField(time_t * time)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
SetWidth(float width)267 BDateField::SetWidth(float width)
268 {
269 fWidth = width;
270 }
271
272
273 float
Width()274 BDateField::Width()
275 {
276 return fWidth;
277 }
278
279
280 void
SetClippedString(const char * string)281 BDateField::SetClippedString(const char* string)
282 {
283 fClippedString = string;
284 }
285
286
287 const char*
ClippedString()288 BDateField::ClippedString()
289 {
290 return fClippedString.String();
291 }
292
293
294 time_t
Seconds()295 BDateField::Seconds()
296 {
297 return fSeconds;
298 }
299
300
301 time_t
UnixTime()302 BDateField::UnixTime()
303 {
304 return fUnixTime;
305 }
306
307
308 // #pragma mark - BDateColumn
309
310
BDateColumn(const char * title,float width,float minWidth,float maxWidth,alignment align)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
DrawField(BField * _field,BRect rect,BView * parent)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
CompareFields(BField * field1,BField * field2)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
BSizeField(off_t size)381 BSizeField::BSizeField(off_t size)
382 :
383 fSize(size)
384 {
385 }
386
387
388 void
SetSize(off_t size)389 BSizeField::SetSize(off_t size)
390 {
391 fSize = size;
392 }
393
394
395 off_t
Size()396 BSizeField::Size()
397 {
398 return fSize;
399 }
400
401
402 // #pragma mark - BSizeColumn
403
404
BSizeColumn(const char * title,float width,float minWidth,float maxWidth,alignment align)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
DrawField(BField * _field,BRect rect,BView * parent)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
CompareFields(BField * field1,BField * field2)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
BIntegerField(int32 number)495 BIntegerField::BIntegerField(int32 number)
496 :
497 fInteger(number)
498 {
499 }
500
501
502 void
SetValue(int32 value)503 BIntegerField::SetValue(int32 value)
504 {
505 fInteger = value;
506 }
507
508
509 int32
Value()510 BIntegerField::Value()
511 {
512 return fInteger;
513 }
514
515
516 // #pragma mark - BIntegerColumn
517
518
BIntegerColumn(const char * title,float width,float minWidth,float maxWidth,alignment align)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
DrawField(BField * field,BRect rect,BView * parent)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
CompareFields(BField * field1,BField * field2)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
GraphColumn(const char * name,float width,float minWidth,float maxWidth,alignment align)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
DrawField(BField * field,BRect rect,BView * parent)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
BBitmapField(BBitmap * bitmap)595 BBitmapField::BBitmapField(BBitmap* bitmap)
596 :
597 fBitmap(bitmap)
598 {
599 }
600
601
602 const BBitmap*
Bitmap()603 BBitmapField::Bitmap()
604 {
605 return fBitmap;
606 }
607
608
609 void
SetBitmap(BBitmap * bitmap)610 BBitmapField::SetBitmap(BBitmap* bitmap)
611 {
612 fBitmap = bitmap;
613 }
614
615
616 // #pragma mark - BBitmapColumn
617
618
BBitmapColumn(const char * title,float width,float minWidth,float maxWidth,alignment align)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
DrawField(BField * field,BRect rect,BView * parent)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
CompareFields(BField *,BField *)671 BBitmapColumn::CompareFields(BField* /*field1*/, BField* /*field2*/)
672 {
673 // Comparing bitmaps doesn't really make sense...
674 return 0;
675 }
676
677
678 bool
AcceptsField(const BField * field) const679 BBitmapColumn::AcceptsField(const BField *field) const
680 {
681 return static_cast<bool>(dynamic_cast<const BBitmapField*>(field));
682 }
683