/
/ File: ColumnTypes.h
/
/ Description: Experimental classes that implement particular column/field
/ data types for use in BColumnListView.
/
/ Copyright 2000+, Be Incorporated, All Rights Reserved
/ Copyright 2024, Haiku, Inc. All Rights Reserved
/
*******************************************************************************/
#include "ColumnTypes.h"
#include <StringFormat.h>
#include <SystemCatalog.h>
#include <View.h>
#include <stdio.h>
using BPrivate::gSystemCatalog;
#undef B_TRANSLATE_COMMENT
#define B_TRANSLATE_COMMENT(str, comment) \
gSystemCatalog.GetString(B_TRANSLATE_MARK_COMMENT(str, comment), \
B_TRANSLATION_CONTEXT, (comment))
#define kTEXT_MARGIN 8
BTitledColumn::BTitledColumn(const char* title, float width, float minWidth,
float maxWidth, alignment align)
:
BColumn(width, minWidth, maxWidth, align),
fTitle(title)
{
font_height fh;
be_plain_font->GetHeight(&fh);
fFontHeight = fh.descent + fh.leading;
}
void
BTitledColumn::DrawTitle(BRect rect, BView* parent)
{
float width = rect.Width() - (2 * kTEXT_MARGIN);
BString out_string(fTitle);
parent->TruncateString(&out_string, B_TRUNCATE_END, width + 2);
DrawString(out_string.String(), parent, rect);
}
void
BTitledColumn::GetColumnName(BString* into) const
{
*into = fTitle;
}
void
BTitledColumn::DrawString(const char* string, BView* parent, BRect rect)
{
float width = rect.Width() - (2 * kTEXT_MARGIN);
float y;
BFont font;
font_height finfo;
parent->GetFont(&font);
font.GetHeight(&finfo);
y = rect.top + finfo.ascent
+ (rect.Height() - ceilf(finfo.ascent + finfo.descent)) / 2.0f;
switch (Alignment()) {
default:
case B_ALIGN_LEFT:
parent->MovePenTo(rect.left + kTEXT_MARGIN, y);
break;
case B_ALIGN_CENTER:
parent->MovePenTo(rect.left + kTEXT_MARGIN
+ ((width - font.StringWidth(string)) / 2), y);
break;
case B_ALIGN_RIGHT:
parent->MovePenTo(rect.right - kTEXT_MARGIN
- font.StringWidth(string), y);
break;
}
parent->DrawString(string);
}
void
BTitledColumn::SetTitle(const char* title)
{
fTitle.SetTo(title);
}
void
BTitledColumn::Title(BString* forTitle) const
{
if (forTitle)
forTitle->SetTo(fTitle.String());
}
float
BTitledColumn::FontHeight() const
{
return fFontHeight;
}
float
BTitledColumn::GetPreferredWidth(BField *_field, BView* parent) const
{
return parent->StringWidth(fTitle.String()) + 2 * kTEXT_MARGIN;
}
BStringField::BStringField(const char* string)
:
fWidth(0),
fString(string),
fClippedString(string)
{
}
void
BStringField::SetString(const char* val)
{
fString = val;
fClippedString = "";
fWidth = 0;
}
const char*
BStringField::String() const
{
return fString.String();
}
void
BStringField::SetWidth(float width)
{
fWidth = width;
}
float
BStringField::Width()
{
return fWidth;
}
void
BStringField::SetClippedString(const char* val)
{
fClippedString = val;
}
bool
BStringField::HasClippedString() const
{
return !fClippedString.IsEmpty();
}
const char*
BStringField::ClippedString()
{
return fClippedString.String();
}
BStringColumn::BStringColumn(const char* title, float width, float minWidth,
float maxWidth, uint32 truncate, alignment align)
:
BTitledColumn(title, width, minWidth, maxWidth, align),
fTruncate(truncate)
{
}
void
BStringColumn::DrawField(BField* _field, BRect rect, BView* parent)
{
float width = rect.Width() - (2 * kTEXT_MARGIN);
BStringField* field = static_cast<BStringField*>(_field);
float fieldWidth = field->Width();
bool updateNeeded = width != fieldWidth;
if (updateNeeded) {
BString out_string(field->String());
float preferredWidth = parent->StringWidth(out_string.String());
if (width < preferredWidth) {
parent->TruncateString(&out_string, fTruncate, width + 2);
field->SetClippedString(out_string.String());
} else
field->SetClippedString("");
field->SetWidth(width);
}
DrawString(field->HasClippedString()
? field->ClippedString()
: field->String(), parent, rect);
}
float
BStringColumn::GetPreferredWidth(BField *_field, BView* parent) const
{
BStringField* field = static_cast<BStringField*>(_field);
return parent->StringWidth(field->String()) + 2 * kTEXT_MARGIN;
}
int
BStringColumn::CompareFields(BField* field1, BField* field2)
{
return ICompare(((BStringField*)field1)->String(),
(((BStringField*)field2)->String()));
}
bool
BStringColumn::AcceptsField(const BField *field) const
{
return static_cast<bool>(dynamic_cast<const BStringField*>(field));
}
BDateField::BDateField(time_t* time)
:
fTime(*localtime(time)),
fUnixTime(*time),
fSeconds(0),
fClippedString(""),
fWidth(0)
{
fSeconds = mktime(&fTime);
}
void
BDateField::SetWidth(float width)
{
fWidth = width;
}
float
BDateField::Width()
{
return fWidth;
}
void
BDateField::SetClippedString(const char* string)
{
fClippedString = string;
}
const char*
BDateField::ClippedString()
{
return fClippedString.String();
}
time_t
BDateField::Seconds()
{
return fSeconds;
}
time_t
BDateField::UnixTime()
{
return fUnixTime;
}
BDateColumn::BDateColumn(const char* title, float width, float minWidth,
float maxWidth, alignment align)
:
BTitledColumn(title, width, minWidth, maxWidth, align),
fTitle(title)
{
}
void
BDateColumn::DrawField(BField* _field, BRect rect, BView* parent)
{
float width = rect.Width() - (2 * kTEXT_MARGIN);
BDateField* field = (BDateField*)_field;
if (field->Width() != rect.Width()) {
char dateString[256];
time_t currentTime = field->UnixTime();
tm time_data;
BFont font;
parent->GetFont(&font);
localtime_r(¤tTime, &time_data);
const BDateFormatStyle dateStyles[] = {
B_FULL_DATE_FORMAT, B_FULL_DATE_FORMAT, B_LONG_DATE_FORMAT, B_LONG_DATE_FORMAT,
B_MEDIUM_DATE_FORMAT, B_SHORT_DATE_FORMAT,
};
const BTimeFormatStyle timeStyles[] = {
B_MEDIUM_TIME_FORMAT, B_SHORT_TIME_FORMAT, B_MEDIUM_TIME_FORMAT, B_SHORT_TIME_FORMAT,
B_SHORT_TIME_FORMAT, B_SHORT_TIME_FORMAT,
};
size_t index;
for (index = 0; index < B_COUNT_OF(dateStyles); index++) {
ssize_t output = fDateTimeFormat.Format(dateString, sizeof(dateString), currentTime,
dateStyles[index], timeStyles[index]);
if (output >= 0 && font.StringWidth(dateString) <= width)
break;
}
if (index == B_COUNT_OF(dateStyles))
fDateFormat.Format(dateString, sizeof(dateString), currentTime, B_SHORT_DATE_FORMAT);
if (font.StringWidth(dateString) > width) {
BString out_string(dateString);
parent->TruncateString(&out_string, B_TRUNCATE_MIDDLE, width + 2);
strcpy(dateString, out_string.String());
}
field->SetClippedString(dateString);
field->SetWidth(width);
}
DrawString(field->ClippedString(), parent, rect);
}
int
BDateColumn::CompareFields(BField* field1, BField* field2)
{
return((BDateField*)field1)->Seconds() - ((BDateField*)field2)->Seconds();
}
BSizeField::BSizeField(off_t size)
:
fSize(size)
{
}
void
BSizeField::SetSize(off_t size)
{
fSize = size;
}
off_t
BSizeField::Size()
{
return fSize;
}
BSizeColumn::BSizeColumn(const char* title, float width, float minWidth,
float maxWidth, alignment align)
:
BTitledColumn(title, width, minWidth, maxWidth, align)
{
}
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "StringForSize"
void
BSizeColumn::DrawField(BField* _field, BRect rect, BView* parent)
{
BFont font;
BString printedSize;
BString string;
float width = rect.Width() - (2 * kTEXT_MARGIN);
double value = ((BSizeField*)_field)->Size();
parent->GetFont(&font);
const char* kFormats[] = {
B_TRANSLATE_MARK_COMMENT("{0, plural, one{%s byte} other{%s bytes}}", "size unit"),
B_TRANSLATE_MARK_COMMENT("%s KiB", "size unit"),
B_TRANSLATE_MARK_COMMENT("%s MiB", "size unit"),
B_TRANSLATE_MARK_COMMENT("%s GiB", "size unit"),
B_TRANSLATE_MARK_COMMENT("%s TiB", "size unit")
};
size_t index = 0;
while (index < B_COUNT_OF(kFormats) - 1 && value >= 1024.0) {
value /= 1024.0;
index++;
}
BString format;
BStringFormat formatter(
gSystemCatalog.GetString(kFormats[index], B_TRANSLATION_CONTEXT, "size unit"));
formatter.Format(format, value);
if (index == 0) {
fNumberFormat.SetPrecision(0);
fNumberFormat.Format(printedSize, value);
string.SetToFormat(format.String(), printedSize.String());
if (font.StringWidth(string) > width) {
BStringFormat formatter(B_TRANSLATE_COMMENT("%s B", "size unit, narrow space"));
format.Truncate(0);
formatter.Format(format, value);
string.SetToFormat(format.String(), printedSize.String());
}
} else {
int precision = 2;
while (precision >= 0) {
fNumberFormat.SetPrecision(precision);
fNumberFormat.Format(printedSize, value);
string.SetToFormat(format.String(), printedSize.String());
if (font.StringWidth(string) <= width)
break;
precision--;
}
}
parent->TruncateString(&string, B_TRUNCATE_MIDDLE, width + 2);
DrawString(string.String(), parent, rect);
}
#undef B_TRANSLATION_CONTEXT
int
BSizeColumn::CompareFields(BField* field1, BField* field2)
{
off_t diff = ((BSizeField*)field1)->Size() - ((BSizeField*)field2)->Size();
if (diff > 0)
return 1;
if (diff < 0)
return -1;
return 0;
}
BIntegerField::BIntegerField(int32 number)
:
fInteger(number)
{
}
void
BIntegerField::SetValue(int32 value)
{
fInteger = value;
}
int32
BIntegerField::Value()
{
return fInteger;
}
BIntegerColumn::BIntegerColumn(const char* title, float width, float minWidth,
float maxWidth, alignment align)
:
BTitledColumn(title, width, minWidth, maxWidth, align)
{
}
void
BIntegerColumn::DrawField(BField *field, BRect rect, BView* parent)
{
BString string;
fNumberFormat.Format(string, (int32)((BIntegerField*)field)->Value());
float width = rect.Width() - (2 * kTEXT_MARGIN);
parent->TruncateString(&string, B_TRUNCATE_MIDDLE, width + 2);
DrawString(string.String(), parent, rect);
}
int
BIntegerColumn::CompareFields(BField *field1, BField *field2)
{
return (((BIntegerField*)field1)->Value() - ((BIntegerField*)field2)->Value());
}
GraphColumn::GraphColumn(const char* name, float width, float minWidth,
float maxWidth, alignment align)
:
BIntegerColumn(name, width, minWidth, maxWidth, align)
{
}
void
GraphColumn::DrawField(BField* field, BRect rect, BView* parent)
{
double fieldValue = ((BIntegerField*)field)->Value();
double percentValue = fieldValue / 100.0;
if (percentValue > 1.0)
percentValue = 1.0;
else if (percentValue < 0.0)
percentValue = 0.0;
BRect graphRect(rect);
graphRect.InsetBy(5, 3);
parent->StrokeRoundRect(graphRect, 2.5, 2.5);
if (percentValue > 0.0) {
graphRect.InsetBy(1, 1);
double value = graphRect.Width() * percentValue;
graphRect.right = graphRect.left + value;
parent->SetHighUIColor(B_NAVIGATION_BASE_COLOR);
parent->FillRect(graphRect);
}
parent->SetDrawingMode(B_OP_INVERT);
parent->SetHighColor(128, 128, 128);
BString percentString;
fNumberFormat.FormatPercent(percentString, percentValue);
float width = be_plain_font->StringWidth(percentString);
parent->MovePenTo(rect.left + rect.Width() / 2 - width / 2, rect.bottom - FontHeight());
parent->DrawString(percentString.String());
}
BBitmapField::BBitmapField(BBitmap* bitmap)
:
fBitmap(bitmap)
{
}
const BBitmap*
BBitmapField::Bitmap()
{
return fBitmap;
}
void
BBitmapField::SetBitmap(BBitmap* bitmap)
{
fBitmap = bitmap;
}
BBitmapColumn::BBitmapColumn(const char* title, float width, float minWidth,
float maxWidth, alignment align)
:
BTitledColumn(title, width, minWidth, maxWidth, align)
{
}
void
BBitmapColumn::DrawField(BField* field, BRect rect, BView* parent)
{
BBitmapField* bitmapField = static_cast<BBitmapField*>(field);
const BBitmap* bitmap = bitmapField->Bitmap();
if (bitmap != NULL) {
float x = 0.0;
BRect r = bitmap->Bounds();
float y = rect.top + ((rect.Height() - r.Height()) / 2);
switch (Alignment()) {
default:
case B_ALIGN_LEFT:
x = rect.left + kTEXT_MARGIN;
break;
case B_ALIGN_CENTER:
x = rect.left + ((rect.Width() - r.Width()) / 2);
break;
case B_ALIGN_RIGHT:
x = rect.right - kTEXT_MARGIN - r.Width();
break;
}
drawing_mode oldMode = parent->DrawingMode();
if (bitmap->ColorSpace() == B_RGBA32
|| bitmap->ColorSpace() == B_RGBA32_BIG) {
parent->SetDrawingMode(B_OP_ALPHA);
parent->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
} else {
parent->SetDrawingMode(B_OP_OVER);
}
parent->DrawBitmap(bitmap, BPoint(x, y));
parent->SetDrawingMode(oldMode);
}
}
int
BBitmapColumn::CompareFields(BField* , BField* )
{
return 0;
}
bool
BBitmapColumn::AcceptsField(const BField *field) const
{
return static_cast<bool>(dynamic_cast<const BBitmapField*>(field));
}