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