Personally I often prefer console applications over GUI applications. However, sometimes it is quite a challenge to display everything well-aligned. Therefore I decided to create a console-based table which makes it possible to display data organized into categories. You can add, remove and update entries and choose between different table designs. This is an example how it looks like.
(Some characters might not be displayed correctly in the Windows console. In the Linux terminal all characters are printed without a problem.)
I'd like to get some feedback what I can improve in this class and if there are any bad habits.
ConsoleTable.h
#ifndef CONSOLETABLE_CONSOLETABLE_H
#define CONSOLETABLE_CONSOLETABLE_H
#include <string>
#include <vector>
#include <iostream>
#include "ConsoleTableRow.h"
#include "ConsoleTableUtils.h"
#include <sstream>
enum TableStyle {
BASIC,
LINED,
DOUBLE_LINE,
};
enum HorizontalSeperator{
SEPERATOR_TOP,
SEPERATOR_MIDDLE,
SEPERATOR_BOTTOM
};
class ConsoleTable {
public:
ConsoleTable(TableStyle style);
void setPadding(unsigned int width);
void addColumn(std::string name);
void addRow(ConsoleTableRow *item);
bool removeRow(int index);
bool editRow(std::string data, int row, int col);
void printTable();
private:
unsigned int padding = 1;
std::vector<std::string> columns;
std::vector<ConsoleTableRow *> entries;
ConsoleTableUtils* utils;
// Table Style variables
std::string style_line_horizontal;
std::string style_line_vertical;
std::string style_line_cross;
std::string style_t_intersect_right;
std::string style_t_intersect_left;
std::string style_t_intersect_top;
std::string style_t_intersect_bottom;
std::string style_edge_topleft;
std::string style_edge_topright;
std::string style_edge_buttomleft;
std::string style_edge_buttomright;
void printHorizontalSeperator(const std::vector<int> &maxWidths, HorizontalSeperator seperator) const;
void setTableStyle(TableStyle style);
};
#endif //CONSOLETABLE_CONSOLETABLE_H
ConsoleTable.cpp
#include "ConsoleTable.h"
ConsoleTable::ConsoleTable(TableStyle style) {
setTableStyle(style);
this->utils = new ConsoleTableUtils();
}
void ConsoleTable::addColumn(std::string name) {
this->columns.push_back(name);
}
void ConsoleTable::printTable() {
// Calculate column maxima
std::vector<int> maxWidths(this->columns.size());
for (int row = 0; row < this->entries.size(); row++) {
for (int col = 0; col < this->columns.size(); col++) {
std::string cellText = this->entries[row]->getEntry()[col];
if (this->columns[col].length() > maxWidths[col])
maxWidths[col] = this->columns[col].length();
if (maxWidths[col] < cellText.length()) {
maxWidths[col] = cellText.length();
}
}
}
printHorizontalSeperator(maxWidths, SEPERATOR_TOP);
// Print column values
for (int col = 0; col < this->columns.size(); col++) {
std::string cellText = this->columns[col];
int len = cellText.length();
std::string paddedText = cellText + std::string(maxWidths[col] - len, ' ');
std::cout << this->style_line_vertical << std::string(this->padding, ' ') << paddedText
<< std::string(this->padding, ' ');
std::cout << (col == this->columns.size() - 1 ? this->style_line_vertical + "\n" : "");
}
printHorizontalSeperator(maxWidths, SEPERATOR_MIDDLE);
// Print cell values
for (int row = 0; row < this->entries.size(); row++) {
for (int col = 0; col < this->columns.size(); col++) {
std::string cellText = this->entries[row]->getEntry()[col];
std::string paddedText = cellText + std::string(maxWidths[col] - cellText.length(), ' ');
std::cout << this->style_line_vertical << std::string(this->padding, ' ') << paddedText
<< std::string(this->padding, ' ');
}
std::cout << this->style_line_vertical << std::endl;
if (row == this->entries.size() - 1)
printHorizontalSeperator(maxWidths, SEPERATOR_BOTTOM);
else
printHorizontalSeperator(maxWidths, SEPERATOR_MIDDLE);
}
}
void ConsoleTable::printHorizontalSeperator(const std::vector<int> &maxWidths, HorizontalSeperator seperator) const {
for (int col = 0; col < columns.size(); ++col) {
switch (seperator) {
case SEPERATOR_TOP: {
std::cout << (col == 0 ? this->style_edge_topleft : "");
std::cout << utils->repeatString(this->style_line_horizontal, this->padding);
std::cout << utils->repeatString(this->style_line_horizontal, maxWidths[col]);
std::cout << utils->repeatString(this->style_line_horizontal, this->padding);
std::cout << (col != columns.size() - 1 ? this->style_t_intersect_top : this->style_edge_topright);
std::cout << (col == columns.size() - 1 ? "\n" : "");
break;
}
case SEPERATOR_MIDDLE: {
std::cout << (col == 0 ? this->style_t_intersect_left : "");
std::cout << utils->repeatString(this->style_line_horizontal, this->padding);
std::cout << utils->repeatString(this->style_line_horizontal, maxWidths[col]);
std::cout << utils->repeatString(this->style_line_horizontal, this->padding);
std::cout << (col != columns.size() - 1 ? this->style_line_cross : this->style_t_intersect_right);
std::cout << (col == columns.size() - 1 ? "\n" : "");
break;
}
case SEPERATOR_BOTTOM: {
std::cout << (col == 0 ? this->style_edge_buttomleft : "");
std::cout << utils->repeatString(this->style_line_horizontal, this->padding);
std::cout << utils->repeatString(this->style_line_horizontal, maxWidths[col]);
std::cout << utils->repeatString(this->style_line_horizontal, this->padding);
std::cout
<< (col != columns.size() - 1 ? this->style_t_intersect_bottom : this->style_edge_buttomright);
std::cout << (col == columns.size() - 1 ? "\n" : "");
break;
}
}
}
}
void ConsoleTable::addRow(ConsoleTableRow *item) {
this->entries.push_back(item);
}
bool ConsoleTable::removeRow(int index) {
if (index > this->entries.size())
return false;
this->entries.erase(this->entries.begin() + index);
return true;
}
bool ConsoleTable::editRow(std::string data, int row, int col) {
if(row > this->entries.size())
return false;
if(col > this->columns.size())
return false;
auto entry = this->entries[row];
entry->editEntry(data, col);
return true;
}
void ConsoleTable::setPadding(unsigned int width) {
this->padding = width;
}
void ConsoleTable::setTableStyle(TableStyle style) {
switch (style) {
case BASIC: {
this->style_line_horizontal = "-";
this->style_line_vertical = "|";
this->style_line_cross = "+";
this->style_t_intersect_right = "+";
this->style_t_intersect_left = "+";
this->style_t_intersect_top = "+";
this->style_t_intersect_bottom = "+";
this->style_edge_topleft = "+";
this->style_edge_topright = "+";
this->style_edge_buttomleft = "+";
this->style_edge_buttomright = "+";
break;
}
case LINED: {
this->style_line_horizontal = "━";
this->style_line_vertical = "┃";
this->style_line_cross = "╋";
this->style_t_intersect_right = "┫";
this->style_t_intersect_left = "┣";
this->style_t_intersect_top = "┳";
this->style_t_intersect_bottom = "┻";
this->style_edge_topleft = "┏";
this->style_edge_topright = "┓";
this->style_edge_buttomleft = "┗";
this->style_edge_buttomright = "┛";
break;
}
case DOUBLE_LINE: {
this->style_line_horizontal = "═";
this->style_line_vertical = "║";
this->style_line_cross = "╬";
this->style_t_intersect_right = "╣";
this->style_t_intersect_left = "╠";
this->style_t_intersect_top = "╦";
this->style_t_intersect_bottom = "╩";
this->style_edge_topleft = "╔";
this->style_edge_topright = "╗";
this->style_edge_buttomleft = "╚";
this->style_edge_buttomright = "╝";
break;
}
}
}
ConsoleTableRow.h
#ifndef CONSOLETABLE_CONSOLETABLEENTRY_H
#define CONSOLETABLE_CONSOLETABLEENTRY_H
#include <string>
#include <vector>
class ConsoleTableRow {
public:
ConsoleTableRow(int width);
void addEntry(std::string data, int column);
void editEntry(std::string data, int column);
std::vector <std::string> getEntry();
private:
std::vector <std::string> row;
};
#endif //CONSOLETABLE_CONSOLETABLEENTRY_H
ConsoleTableRow.cpp
#include "ConsoleTableRow.h"
ConsoleTableRow::ConsoleTableRow(int width) {
this->row.resize(width);
}
void ConsoleTableRow::addEntry(std::string data, int column) {
row[column] = data;
}
std::vector<std::string> ConsoleTableRow::getEntry() {
return this->row;
}
void ConsoleTableRow::editEntry(std::string data, int column) {
this->row[column] = data;
}
ConsoleTableUtils.h
#ifndef CONSOLETABLE_CONSOLETABLEUTILS_H
#define CONSOLETABLE_CONSOLETABLEUTILS_H
#include <string>
#include <sstream>
class ConsoleTableUtils {
public:
std::string repeatString(std::string input, int n) const;
};
#endif //CONSOLETABLE_CONSOLETABLEUTILS_H
ConsoleTableUtils.cpp
#include "ConsoleTableUtils.h"
std::string ConsoleTableUtils::repeatString(std::string input, int n) const {
std::ostringstream os;
for (int i = 0; i < n; i++)
os << input;
return os.str();
}
Here is an example to generate use the classes. This example will generate the tables that can be seen in the screenshot above.
#include <iostream>
#include <unistd.h>
#include "ConsoleTable.h"
int main() {
ConsoleTable ct(BASIC);
ct.setPadding(1);
ct.addColumn("Country");
ct.addColumn("Name");
ct.addColumn("Profession");
ct.addColumn("Age");
auto entry = new ConsoleTableRow(4);
entry->addEntry("Germany", 0);
entry->addEntry("Michael", 1);
entry->addEntry("Computer Engineer", 2);
entry->addEntry("19", 3);
ct.addRow(entry);
auto entry2 = new ConsoleTableRow(4);
entry2->addEntry("England", 0);
entry2->addEntry("Robert", 1);
entry2->addEntry("Artist", 2);
entry2->addEntry("34", 3);
ct.addRow(entry2);
auto entry3 = new ConsoleTableRow(4);
entry3->addEntry("United Kingdom", 0);
entry3->addEntry("Julia", 1);
entry3->addEntry("Designer", 2);
entry3->addEntry("42", 3);
ct.addRow(entry3);
auto entry4 = new ConsoleTableRow(4);
entry4->addEntry("United Staates", 0);
entry4->addEntry("Jo", 1);
entry4->addEntry("Actor", 2);
entry4->addEntry("21", 3);
ct.addRow(entry4);
// Print all entries
ct.printTable();
return 0;
}
The project repository can be found on GitHub.