haiku/src/kits/shared/TextTable.cpp

281 lines
5.2 KiB
C++

/*
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include <TextTable.h>
#include <stdio.h>
#include <algorithm>
namespace BPrivate {
// #pragma mark - Column
struct TextTable::Column {
Column(const BString& title, enum alignment align, bool canTruncate)
:
fTitle(title),
fAlignment(align),
fCanBeTruncated(canTruncate),
fNeededWidth(0),
fWidth(0)
{
UpdateNeededWidth(fTitle);
fMinWidth = fNeededWidth;
}
const BString& Title() const
{
return fTitle;
}
enum alignment Alignment() const
{
return fAlignment;
}
bool CanBeTruncated() const
{
return fCanBeTruncated;
}
int32 NeededWidth() const
{
return fNeededWidth;
}
int32 MinWidth() const
{
return fMinWidth;
}
int32 Width() const
{
return fWidth;
}
void SetWidth(int32 width)
{
fWidth = width;
}
void UpdateNeededWidth(const BString& text)
{
// TODO: Full-width character support.
int32 textWidth = text.CountChars();
if (textWidth > fNeededWidth)
fNeededWidth = textWidth;
}
BString Format(const BString& text)
{
// TODO: Full-width character support.
int32 textWidth = text.CountChars();
if (textWidth == fWidth)
return text;
// truncate, if too long
if (textWidth > fWidth) {
BString result(text);
result.TruncateChars(fWidth);
return result;
}
// align, if too short
int32 missing = fWidth - textWidth;
switch (fAlignment) {
case B_ALIGN_LEFT:
default:
{
BString result(text);
result.Append(' ', missing);
return result;
}
case B_ALIGN_RIGHT:
{
BString result;
result.Append(' ', missing);
result.Append(text);
return result;
}
case B_ALIGN_CENTER:
{
BString result;
result.Append(' ', missing / 2);
result.Append(text);
result.Append(' ', missing - missing / 2);
return result;
}
}
}
private:
BString fTitle;
enum alignment fAlignment;
bool fCanBeTruncated;
int32 fNeededWidth;
int32 fMinWidth;
int32 fWidth;
};
// #pragma mark - TextTable
TextTable::TextTable()
:
fColumns(10, true),
fRows(100, true)
{
}
TextTable::~TextTable()
{
}
int32
TextTable::CountColumns() const
{
return fColumns.CountItems();
}
void
TextTable::AddColumn(const BString& title, enum alignment align,
bool canTruncate)
{
Column* column = new Column(title, align, canTruncate);
if (!fColumns.AddItem(column)) {
delete column;
throw std::bad_alloc();
}
}
int32
TextTable::CountRows() const
{
return fRows.CountItems();
}
BString
TextTable::TextAt(int32 rowIndex, int32 columnIndex) const
{
BStringList* row = fRows.ItemAt(rowIndex);
if (row == NULL)
return BString();
return row->StringAt(columnIndex);
}
void
TextTable::SetTextAt(int32 rowIndex, int32 columnIndex, const BString& text)
{
// If necessary append empty rows up to the specified row index.
while (rowIndex >= fRows.CountItems()) {
BStringList* row = new BStringList();
if (!fRows.AddItem(row)) {
delete row;
throw std::bad_alloc();
}
}
// If necessary append empty strings up to the specified column index.
BStringList* row = fRows.ItemAt(rowIndex);
while (columnIndex >= row->CountStrings()) {
if (!row->Add(BString()))
throw std::bad_alloc();
}
// set the text
if (!row->Replace(columnIndex, text))
throw std::bad_alloc();
}
void
TextTable::Print(int32 maxWidth)
{
int32 columnCount = fColumns.CountItems();
if (columnCount == 0)
return;
// determine the column widths
int32 rowCount = fRows.CountItems();
for (int32 rowIndex = 0; rowIndex < rowCount; rowIndex++) {
BStringList* row = fRows.ItemAt(rowIndex);
int32 rowColumnCount = std::min(row->CountStrings(), columnCount);
for (int32 columnIndex = 0; columnIndex < rowColumnCount;
columnIndex++) {
fColumns.ItemAt(columnIndex)->UpdateNeededWidth(
row->StringAt(columnIndex));
}
}
int32 neededWidth = (columnCount - 1) * 2;
// spacing
for (int32 i = 0; i < columnCount; i++)
neededWidth += fColumns.ItemAt(i)->NeededWidth();
int32 width = neededWidth;
int32 missingWidth = neededWidth - std::min(maxWidth, neededWidth);
for (int32 i = 0; i < columnCount; i++) {
Column* column = fColumns.ItemAt(i);
if (missingWidth > 0 && column->CanBeTruncated()) {
int32 truncateBy = std::min(missingWidth,
column->NeededWidth() - column->MinWidth());
column->SetWidth(column->NeededWidth() - truncateBy);
missingWidth -= truncateBy;
width -= truncateBy;
} else
column->SetWidth(column->NeededWidth());
}
// print the header
BString line;
for (int32 i = 0; i < columnCount; i++) {
if (i > 0)
line << " ";
Column* column = fColumns.ItemAt(i);
line << column->Format(column->Title());
}
line << '\n';
fputs(line.String(), stdout);
line.SetTo('-', width);
line << '\n';
fputs(line.String(), stdout);
// print the rows
for (int32 rowIndex = 0; rowIndex < rowCount; rowIndex++) {
line.Truncate(0);
BStringList* row = fRows.ItemAt(rowIndex);
for (int32 columnIndex = 0; columnIndex < columnCount; columnIndex++) {
if (columnIndex > 0)
line << " ";
line << fColumns.ItemAt(columnIndex)->Format(
row->StringAt(columnIndex));
}
line << '\n';
fputs(line.String(), stdout);
}
}
} // namespace BPrivate