xref: /haiku/src/kits/shared/TextTable.cpp (revision 1deede7388b04dbeec5af85cae7164735ea9e70d)
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 
14 namespace BPrivate {
15 
16 
17 // #pragma mark - Column
18 
19 
20 struct 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 
120 private:
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 
133 TextTable::TextTable()
134 	:
135 	fColumns(10, true),
136 	fRows(100, true)
137 {
138 }
139 
140 
141 TextTable::~TextTable()
142 {
143 }
144 
145 
146 int32
147 TextTable::CountColumns() const
148 {
149 	return fColumns.CountItems();
150 }
151 
152 
153 void
154 TextTable::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 
165 int32
166 TextTable::CountRows() const
167 {
168 	return fRows.CountItems();
169 }
170 
171 
172 BString
173 TextTable::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 
182 void
183 TextTable::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 
207 void
208 TextTable::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