xref: /haiku/src/kits/interface/ColumnTypes.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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(&currentTime, &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