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 #include <ctype.h>
11 #include <utf8_functions.h>
12
13 #include <algorithm>
14
15
16 namespace BPrivate {
17
18
19 // #pragma mark - Column
20
21
22 struct TextTable::Column {
ColumnBPrivate::TextTable::Column23 Column(const BString& title, enum alignment align, bool canTruncate)
24 :
25 fTitle(title),
26 fAlignment(align),
27 fCanBeTruncated(canTruncate),
28 fNeededWidth(0),
29 fWidth(0)
30 {
31 UpdateNeededWidth(fTitle);
32 fMinWidth = fNeededWidth;
33 }
34
TitleBPrivate::TextTable::Column35 const BString& Title() const
36 {
37 return fTitle;
38 }
39
AlignmentBPrivate::TextTable::Column40 enum alignment Alignment() const
41 {
42 return fAlignment;
43 }
44
CanBeTruncatedBPrivate::TextTable::Column45 bool CanBeTruncated() const
46 {
47 return fCanBeTruncated;
48 }
49
NeededWidthBPrivate::TextTable::Column50 int32 NeededWidth() const
51 {
52 return fNeededWidth;
53 }
54
MinWidthBPrivate::TextTable::Column55 int32 MinWidth() const
56 {
57 return fMinWidth;
58 }
59
WidthBPrivate::TextTable::Column60 int32 Width() const
61 {
62 return fWidth;
63 }
64
SetWidthBPrivate::TextTable::Column65 void SetWidth(int32 width)
66 {
67 fWidth = width;
68 }
69
TextWidthBPrivate::TextTable::Column70 static int32 TextWidth(const BString& text)
71 {
72 // TODO: Full-width character support.
73 int32 textWidth = 0;
74 const char* string = text.String(), *stringEnd = text.String() + text.Length();
75 while (string < stringEnd) {
76 uint32 charLen = UTF8NextCharLen(string, stringEnd - string);
77 if (charLen == 1 && string[0] == '\033') {
78 // ANSI escape code.
79 charLen++;
80 if (string[charLen - 1] == '[') {
81 // Keep going until we hit an end character.
82 while (!isalpha(string[charLen - 1]) && string[charLen - 1] != '\0')
83 charLen++;
84 }
85 } else {
86 textWidth++;
87 }
88 string += charLen;
89 }
90 return textWidth;
91 }
92
UpdateNeededWidthBPrivate::TextTable::Column93 void UpdateNeededWidth(const BString& text)
94 {
95 int32 textWidth = TextWidth(text);
96 if (textWidth > fNeededWidth)
97 fNeededWidth = textWidth;
98 }
99
FormatBPrivate::TextTable::Column100 BString Format(const BString& text)
101 {
102 int32 textWidth = TextWidth(text);
103 if (textWidth == fWidth)
104 return text;
105
106 // truncate, if too long
107 if (textWidth > fWidth) {
108 BString result(text);
109 result.TruncateChars(fWidth);
110 return result;
111 }
112
113 // align, if too short
114 int32 missing = fWidth - textWidth;
115 switch (fAlignment) {
116 case B_ALIGN_LEFT:
117 default:
118 {
119 BString result(text);
120 result.Append(' ', missing);
121 return result;
122 }
123
124 case B_ALIGN_RIGHT:
125 {
126 BString result;
127 result.Append(' ', missing);
128 result.Append(text);
129 return result;
130 }
131
132 case B_ALIGN_CENTER:
133 {
134 BString result;
135 result.Append(' ', missing / 2);
136 result.Append(text);
137 result.Append(' ', missing - missing / 2);
138 return result;
139 }
140 }
141 }
142
143 private:
144 BString fTitle;
145 enum alignment fAlignment;
146 bool fCanBeTruncated;
147 int32 fNeededWidth;
148 int32 fMinWidth;
149 int32 fWidth;
150 };
151
152
153 // #pragma mark - TextTable
154
155
TextTable()156 TextTable::TextTable()
157 :
158 fColumns(10, true),
159 fRows(100, true)
160 {
161 }
162
163
~TextTable()164 TextTable::~TextTable()
165 {
166 }
167
168
169 int32
CountColumns() const170 TextTable::CountColumns() const
171 {
172 return fColumns.CountItems();
173 }
174
175
176 void
AddColumn(const BString & title,enum alignment align,bool canTruncate)177 TextTable::AddColumn(const BString& title, enum alignment align,
178 bool canTruncate)
179 {
180 Column* column = new Column(title, align, canTruncate);
181 if (!fColumns.AddItem(column)) {
182 delete column;
183 throw std::bad_alloc();
184 }
185 }
186
187
188 int32
CountRows() const189 TextTable::CountRows() const
190 {
191 return fRows.CountItems();
192 }
193
194
195 BString
TextAt(int32 rowIndex,int32 columnIndex) const196 TextTable::TextAt(int32 rowIndex, int32 columnIndex) const
197 {
198 BStringList* row = fRows.ItemAt(rowIndex);
199 if (row == NULL)
200 return BString();
201 return row->StringAt(columnIndex);
202 }
203
204
205 void
SetTextAt(int32 rowIndex,int32 columnIndex,const BString & text)206 TextTable::SetTextAt(int32 rowIndex, int32 columnIndex, const BString& text)
207 {
208 // If necessary append empty rows up to the specified row index.
209 while (rowIndex >= fRows.CountItems()) {
210 BStringList* row = new BStringList();
211 if (!fRows.AddItem(row)) {
212 delete row;
213 throw std::bad_alloc();
214 }
215 }
216
217 // If necessary append empty strings up to the specified column index.
218 BStringList* row = fRows.ItemAt(rowIndex);
219 while (columnIndex >= row->CountStrings()) {
220 if (!row->Add(BString()))
221 throw std::bad_alloc();
222 }
223
224 // set the text
225 if (!row->Replace(columnIndex, text))
226 throw std::bad_alloc();
227 }
228
229
230 void
Print(int32 maxWidth)231 TextTable::Print(int32 maxWidth)
232 {
233 int32 columnCount = fColumns.CountItems();
234 if (columnCount == 0)
235 return;
236
237 // determine the column widths
238 int32 rowCount = fRows.CountItems();
239 for (int32 rowIndex = 0; rowIndex < rowCount; rowIndex++) {
240 BStringList* row = fRows.ItemAt(rowIndex);
241 int32 rowColumnCount = std::min(row->CountStrings(), columnCount);
242 for (int32 columnIndex = 0; columnIndex < rowColumnCount;
243 columnIndex++) {
244 fColumns.ItemAt(columnIndex)->UpdateNeededWidth(
245 row->StringAt(columnIndex));
246 }
247 }
248
249 int32 neededWidth = (columnCount - 1) * 2;
250 // spacing
251 for (int32 i = 0; i < columnCount; i++)
252 neededWidth += fColumns.ItemAt(i)->NeededWidth();
253
254 int32 width = neededWidth;
255 int32 missingWidth = neededWidth - std::min(maxWidth, neededWidth);
256
257 for (int32 i = 0; i < columnCount; i++) {
258 Column* column = fColumns.ItemAt(i);
259 if (missingWidth > 0 && column->CanBeTruncated()) {
260 int32 truncateBy = std::min(missingWidth,
261 column->NeededWidth() - column->MinWidth());
262 column->SetWidth(column->NeededWidth() - truncateBy);
263 missingWidth -= truncateBy;
264 width -= truncateBy;
265 } else
266 column->SetWidth(column->NeededWidth());
267 }
268
269 // print the header
270 BString line;
271 for (int32 i = 0; i < columnCount; i++) {
272 if (i > 0)
273 line << " ";
274
275 Column* column = fColumns.ItemAt(i);
276 line << column->Format(column->Title());
277 }
278 line << '\n';
279 fputs(line.String(), stdout);
280
281 line.SetTo('-', width);
282 line << '\n';
283 fputs(line.String(), stdout);
284
285 // print the rows
286 for (int32 rowIndex = 0; rowIndex < rowCount; rowIndex++) {
287 line.Truncate(0);
288 BStringList* row = fRows.ItemAt(rowIndex);
289 for (int32 columnIndex = 0; columnIndex < columnCount; columnIndex++) {
290 if (columnIndex > 0)
291 line << " ";
292
293 line << fColumns.ItemAt(columnIndex)->Format(
294 row->StringAt(columnIndex));
295 }
296
297 line << '\n';
298 fputs(line.String(), stdout);
299 }
300 }
301
302
303 } // namespace BPrivate
304