1/*
2 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <TextTable.h>
8
9#include <stdio.h>
10
11#include <algorithm>
12
13
14namespace BPrivate {
15
16
17// #pragma mark - Column
18
19
20struct TextTable::Column {
21	Column(const BString& title, enum alignment align, bool canTruncate)
22		:
23		fTitle(title),
24		fAlignment(align),
25		fCanBeTruncated(canTruncate),
26		fNeededWidth(0),
27		fWidth(0)
28	{
29		UpdateNeededWidth(fTitle);
30		fMinWidth = fNeededWidth;
31	}
32
33	const BString& Title() const
34	{
35		return fTitle;
36	}
37
38	enum alignment Alignment() const
39	{
40		return fAlignment;
41	}
42
43	bool CanBeTruncated() const
44	{
45		return fCanBeTruncated;
46	}
47
48	int32 NeededWidth() const
49	{
50		return fNeededWidth;
51	}
52
53	int32 MinWidth() const
54	{
55		return fMinWidth;
56	}
57
58	int32 Width() const
59	{
60		return fWidth;
61	}
62
63	void SetWidth(int32 width)
64	{
65		fWidth = width;
66	}
67
68	void UpdateNeededWidth(const BString& text)
69	{
70		// TODO: Full-width character support.
71		int32 textWidth = text.CountChars();
72		if (textWidth > fNeededWidth)
73			fNeededWidth = textWidth;
74	}
75
76	BString Format(const BString& text)
77	{
78		// TODO: Full-width character support.
79		int32 textWidth = text.CountChars();
80		if (textWidth == fWidth)
81			return text;
82
83		// truncate, if too long
84		if (textWidth > fWidth) {
85			BString result(text);
86			result.TruncateChars(fWidth);
87			return result;
88		}
89
90		// align, if too short
91		int32 missing = fWidth - textWidth;
92		switch (fAlignment) {
93			case B_ALIGN_LEFT:
94			default:
95			{
96				BString result(text);
97				result.Append(' ', missing);
98				return result;
99			}
100
101			case B_ALIGN_RIGHT:
102			{
103				BString result;
104				result.Append(' ', missing);
105				result.Append(text);
106				return result;
107			}
108
109			case B_ALIGN_CENTER:
110			{
111				BString result;
112				result.Append(' ', missing / 2);
113				result.Append(text);
114				result.Append(' ', missing - missing / 2);
115				return result;
116			}
117		}
118	}
119
120private:
121	BString			fTitle;
122	enum alignment fAlignment;
123	bool			fCanBeTruncated;
124	int32			fNeededWidth;
125	int32			fMinWidth;
126	int32			fWidth;
127};
128
129
130// #pragma mark - TextTable
131
132
133TextTable::TextTable()
134	:
135	fColumns(10, true),
136	fRows(100, true)
137{
138}
139
140
141TextTable::~TextTable()
142{
143}
144
145
146int32
147TextTable::CountColumns() const
148{
149	return fColumns.CountItems();
150}
151
152
153void
154TextTable::AddColumn(const BString& title, enum alignment align,
155	bool canTruncate)
156{
157	Column* column = new Column(title, align, canTruncate);
158	if (!fColumns.AddItem(column)) {
159		delete column;
160		throw std::bad_alloc();
161	}
162}
163
164
165int32
166TextTable::CountRows() const
167{
168	return fRows.CountItems();
169}
170
171
172BString
173TextTable::TextAt(int32 rowIndex, int32 columnIndex) const
174{
175	BStringList* row = fRows.ItemAt(rowIndex);
176	if (row == NULL)
177		return BString();
178	return row->StringAt(columnIndex);
179}
180
181
182void
183TextTable::SetTextAt(int32 rowIndex, int32 columnIndex, const BString& text)
184{
185	// If necessary append empty rows up to the specified row index.
186	while (rowIndex >= fRows.CountItems()) {
187		BStringList* row = new BStringList();
188		if (!fRows.AddItem(row)) {
189			delete row;
190			throw std::bad_alloc();
191		}
192	}
193
194	// If necessary append empty strings up to the specified column index.
195	BStringList* row = fRows.ItemAt(rowIndex);
196	while (columnIndex >= row->CountStrings()) {
197		if (!row->Add(BString()))
198			throw std::bad_alloc();
199	}
200
201	// set the text
202	if (!row->Replace(columnIndex, text))
203		throw std::bad_alloc();
204}
205
206
207void
208TextTable::Print(int32 maxWidth)
209{
210	int32 columnCount = fColumns.CountItems();
211	if (columnCount == 0)
212		return;
213
214	// determine the column widths
215	int32 rowCount = fRows.CountItems();
216	for (int32 rowIndex = 0; rowIndex < rowCount; rowIndex++) {
217		BStringList* row = fRows.ItemAt(rowIndex);
218		int32 rowColumnCount = std::min(row->CountStrings(), columnCount);
219		for (int32 columnIndex = 0; columnIndex < rowColumnCount;
220			columnIndex++) {
221			fColumns.ItemAt(columnIndex)->UpdateNeededWidth(
222				row->StringAt(columnIndex));
223		}
224	}
225
226	int32 neededWidth = (columnCount - 1) * 2;
227		// spacing
228	for (int32 i = 0; i < columnCount; i++)
229		neededWidth += fColumns.ItemAt(i)->NeededWidth();
230
231	int32 width = neededWidth;
232	int32 missingWidth = neededWidth - std::min(maxWidth, neededWidth);
233
234	for (int32 i = 0; i < columnCount; i++) {
235		Column* column = fColumns.ItemAt(i);
236		if (missingWidth > 0 && column->CanBeTruncated()) {
237			int32 truncateBy = std::min(missingWidth,
238				column->NeededWidth() - column->MinWidth());
239			column->SetWidth(column->NeededWidth() - truncateBy);
240			missingWidth -= truncateBy;
241			width -= truncateBy;
242		} else
243			column->SetWidth(column->NeededWidth());
244	}
245
246	// print the header
247	BString line;
248	for (int32 i = 0; i < columnCount; i++) {
249		if (i > 0)
250			line << "  ";
251
252		Column* column = fColumns.ItemAt(i);
253		line << column->Format(column->Title());
254	}
255	line << '\n';
256	fputs(line.String(), stdout);
257
258	line.SetTo('-', width);
259	line << '\n';
260	fputs(line.String(), stdout);
261
262	// print the rows
263	for (int32 rowIndex = 0; rowIndex < rowCount; rowIndex++) {
264		line.Truncate(0);
265		BStringList* row = fRows.ItemAt(rowIndex);
266		for (int32 columnIndex = 0; columnIndex < columnCount; columnIndex++) {
267			if (columnIndex > 0)
268				line << "  ";
269
270			line << fColumns.ItemAt(columnIndex)->Format(
271				row->StringAt(columnIndex));
272		}
273
274		line << '\n';
275		fputs(line.String(), stdout);
276	}
277}
278
279
280} // namespace BPrivate
281