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