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(&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
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