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
26#undef B_TRANSLATION_CONTEXT
27#define B_TRANSLATION_CONTEXT "ColumnTypes"
28
29#define kTEXT_MARGIN	8
30
31
32BTitledColumn::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
45void
46BTitledColumn::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
56void
57BTitledColumn::GetColumnName(BString* into) const
58{
59	*into = fTitle;
60}
61
62
63void
64BTitledColumn::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
97void
98BTitledColumn::SetTitle(const char* title)
99{
100	fTitle.SetTo(title);
101}
102
103
104void
105BTitledColumn::Title(BString* forTitle) const
106{
107	if (forTitle)
108		forTitle->SetTo(fTitle.String());
109}
110
111
112float
113BTitledColumn::FontHeight() const
114{
115	return fFontHeight;
116}
117
118
119float
120BTitledColumn::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
129BStringField::BStringField(const char* string)
130	:
131	fWidth(0),
132	fString(string),
133	fClippedString(string)
134{
135}
136
137
138void
139BStringField::SetString(const char* val)
140{
141	fString = val;
142	fClippedString = "";
143	fWidth = 0;
144}
145
146
147const char*
148BStringField::String() const
149{
150	return fString.String();
151}
152
153
154void
155BStringField::SetWidth(float width)
156{
157	fWidth = width;
158}
159
160
161float
162BStringField::Width()
163{
164	return fWidth;
165}
166
167
168void
169BStringField::SetClippedString(const char* val)
170{
171	fClippedString = val;
172}
173
174
175bool
176BStringField::HasClippedString() const
177{
178	return !fClippedString.IsEmpty();
179}
180
181
182const char*
183BStringField::ClippedString()
184{
185	return fClippedString.String();
186}
187
188
189// #pragma mark - BStringColumn
190
191
192BStringColumn::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
201void
202BStringColumn::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
226float
227BStringColumn::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
234int
235BStringColumn::CompareFields(BField* field1, BField* field2)
236{
237	return ICompare(((BStringField*)field1)->String(),
238		(((BStringField*)field2)->String()));
239}
240
241
242bool
243BStringColumn::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
252BDateField::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
264void
265BDateField::SetWidth(float width)
266{
267	fWidth = width;
268}
269
270
271float
272BDateField::Width()
273{
274	return fWidth;
275}
276
277
278void
279BDateField::SetClippedString(const char* string)
280{
281	fClippedString = string;
282}
283
284
285const char*
286BDateField::ClippedString()
287{
288	return fClippedString.String();
289}
290
291
292time_t
293BDateField::Seconds()
294{
295	return fSeconds;
296}
297
298
299time_t
300BDateField::UnixTime()
301{
302	return fUnixTime;
303}
304
305
306// #pragma mark - BDateColumn
307
308
309BDateColumn::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
318void
319BDateColumn::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
369int
370BDateColumn::CompareFields(BField* field1, BField* field2)
371{
372	return((BDateField*)field1)->Seconds() - ((BDateField*)field2)->Seconds();
373}
374
375
376// #pragma mark - BSizeField
377
378
379BSizeField::BSizeField(off_t size)
380	:
381	fSize(size)
382{
383}
384
385
386void
387BSizeField::SetSize(off_t size)
388{
389	fSize = size;
390}
391
392
393off_t
394BSizeField::Size()
395{
396	return fSize;
397}
398
399
400// #pragma mark - BSizeColumn
401
402
403BSizeColumn::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
411void
412BSizeColumn::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
468int
469BSizeColumn::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
483BIntegerField::BIntegerField(int32 number)
484	:
485	fInteger(number)
486{
487}
488
489
490void
491BIntegerField::SetValue(int32 value)
492{
493	fInteger = value;
494}
495
496
497int32
498BIntegerField::Value()
499{
500	return fInteger;
501}
502
503
504// #pragma mark - BIntegerColumn
505
506
507BIntegerColumn::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
515void
516BIntegerColumn::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
527int
528BIntegerColumn::CompareFields(BField *field1, BField *field2)
529{
530	return (((BIntegerField*)field1)->Value() - ((BIntegerField*)field2)->Value());
531}
532
533
534// #pragma mark - GraphColumn
535
536
537GraphColumn::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
545void
546GraphColumn::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
583BBitmapField::BBitmapField(BBitmap* bitmap)
584	:
585	fBitmap(bitmap)
586{
587}
588
589
590const BBitmap*
591BBitmapField::Bitmap()
592{
593	return fBitmap;
594}
595
596
597void
598BBitmapField::SetBitmap(BBitmap* bitmap)
599{
600	fBitmap = bitmap;
601}
602
603
604// #pragma mark - BBitmapColumn
605
606
607BBitmapColumn::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
615void
616BBitmapColumn::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
658int
659BBitmapColumn::CompareFields(BField* /*field1*/, BField* /*field2*/)
660{
661	// Comparing bitmaps doesn't really make sense...
662	return 0;
663}
664
665
666bool
667BBitmapColumn::AcceptsField(const BField *field) const
668{
669	return static_cast<bool>(dynamic_cast<const BBitmapField*>(field));
670}
671