- Documented the new layout algorithm.
- Used Nibbler for decomposing fields into word chunks.
- Implemented variable intra padding.
- Implemented variable left margin.
- Implemented variable extra padding.
- Implemented colored headers.
- Implemented wrappable headers.
- Eliminated need to specify fixed column size.
This commit is contained in:
Paul Beckingham 2011-04-29 01:45:10 -04:00
parent 66afc7c057
commit 4dca2a5a2d
9 changed files with 186 additions and 87 deletions

View file

@ -34,6 +34,7 @@
View::View () View::View ()
: _width (0) : _width (0)
, _left_margin (0) , _left_margin (0)
, _header (0)
, _odd (0) , _odd (0)
, _even (0) , _even (0)
, _intra_padding (1) , _intra_padding (1)
@ -53,17 +54,41 @@ View::~View ()
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// +-------+ +-------+ +-------+ // |<---------- terminal width ---------->|
// |header | |header | |header | //
// +--+--+-------+--+-------+--+-------+--+ // +-------+ +-------+ +-------+
// |ma|ex|cell |in|cell |in|cell |ex| // |header | |header | |header |
// +--+--+-------+--+-------+--+-------+--+ // +--+--+-------+--+-------+--+-------+--+
// |ma|ex|cell |in|cell |in|cell |ex| // |ma|ex|cell |in|cell |in|cell |ex|
// +--+--+-------+--+-------+--+-------+--+ // +--+--+-------+--+-------+--+-------+--+
// |ma|ex|cell |in|cell |in|cell |ex|
// +--+--+-------+--+-------+--+-------+--+
//
// margin - indentation for the whole table
// extrapadding - left and right padding for the whole table
// intrapadding - padding between columns
//
//
// Layout Algorithm:
// - Height is irrelevant
// - Determine the usable horizontal space for N columns:
//
// usable = width - ma - (ex * 2) - (in * (N - 1))
//
// - Look at every column, for every task, and determine the minimum and
// maximum widths. The minimum is the length of the largest indivisible
// word, and the maximum is the full length of the value.
// - If there is sufficient terminal width to display every task using the
// maximum width, then do so.
// - If there is insufficient terminal width to display every task using the
// minimum width, then there is no layout solution. Error.
// - Otherwise there is a need for column wrapping. Calculate the overage,
// which is the difference between the sum of the minimum widths and the
// usable width.
// - Start by using all the minimum column widths, and distribute the overage
// among all columns, one character at a time, while the column width is
// less than the maximum width, and while there is overage remaining.
// //
// margin
// extrapadding
// intrapadding
std::string View::render (std::vector <Task>& data, std::vector <int>& sequence) std::string View::render (std::vector <Task>& data, std::vector <int>& sequence)
{ {
// Determine minimal, ideal column widths. // Determine minimal, ideal column widths.
@ -80,6 +105,7 @@ std::string View::render (std::vector <Task>& data, std::vector <int>& sequence)
std::vector <Task>::iterator d; std::vector <Task>::iterator d;
for (d = data.begin (); d != data.end (); ++d) for (d = data.begin (); d != data.end (); ++d)
{ {
// Determine minimum and ideal width for this column.
int min; int min;
int ideal; int ideal;
(*i)->measure (*d, min, ideal); (*i)->measure (*d, min, ideal);
@ -92,48 +118,48 @@ std::string View::render (std::vector <Task>& data, std::vector <int>& sequence)
ideal.push_back (global_ideal); ideal.push_back (global_ideal);
} }
// TODO Remove // // TODO Remove
std::string combined; // std::string combined;
join (combined, ",", minimal); // join (combined, ",", minimal);
std::cout << "# minimal " << combined << "\n"; // std::cout << "# minimal " << combined << "\n";
join (combined, ",", ideal); // join (combined, ",", ideal);
std::cout << "# ideal " << combined << "\n"; // std::cout << "# ideal " << combined << "\n";
// Sum the minimal widths. // Sum the minimal widths.
int sum_minimal = 0; int sum_minimal = 0;
std::vector <int>::iterator c; std::vector <int>::iterator c;
for (c = minimal.begin (); c != minimal.end (); ++c) for (c = minimal.begin (); c != minimal.end (); ++c)
sum_minimal += *c; sum_minimal += *c;
std::cout << "# sum_minimal " << sum_minimal << "\n"; // std::cout << "# sum_minimal " << sum_minimal << "\n";
// Sum the ideal widths. // Sum the ideal widths.
int sum_ideal = 0; int sum_ideal = 0;
for (c = ideal.begin (); c != ideal.end (); ++c) for (c = ideal.begin (); c != ideal.end (); ++c)
sum_ideal += *c; sum_ideal += *c;
std::cout << "# sum_ideal " << sum_ideal << "\n"; // std::cout << "# sum_ideal " << sum_ideal << "\n";
// Calculate final column widths. // Calculate final column widths.
int overage = _width int overage = _width
- _left_margin - _left_margin
- (2 * _extra_padding) - (2 * _extra_padding)
- ((_columns.size () - 1) * _intra_padding); - ((_columns.size () - 1) * _intra_padding);
std::cout << "# width " << _width << "\n"; // std::cout << "# width " << _width << "\n";
std::vector <int> widths; std::vector <int> widths;
if (sum_ideal <= overage) if (sum_ideal <= overage)
{ // {
std::cout << "# ideal case: " << sum_ideal << " <= " << overage << "\n"; // std::cout << "# ideal case: " << sum_ideal << " <= " << overage << "\n";
widths = ideal; widths = ideal;
} // }
else if (sum_minimal > overage) else if (sum_minimal > overage)
{ // {
throw std::string ("There is not enough horizontal width to display the results."); throw std::string ("There is not enough horizontal width to display the results.");
} // }
else else
{ {
widths = minimal; widths = minimal;
overage -= sum_minimal; overage -= sum_minimal;
std::cout << "# overage " << overage << "\n"; // std::cout << "# overage " << overage << "\n";
// Spread 'overage' among columns where width[i] < ideal[i] // Spread 'overage' among columns where width[i] < ideal[i]
while (overage) while (overage)
@ -147,24 +173,92 @@ std::string View::render (std::vector <Task>& data, std::vector <int>& sequence)
} }
} }
} }
//
join (combined, ",", widths); // join (combined, ",", widths);
std::cout << "# final widths " << combined << "\n"; // std::cout << "# final widths " << combined << "\n";
} }
// TODO Compose column headers. // Compose column headers.
int max_lines = 0;
std::vector <std::vector <std::string> > headers;
for (int c = 0; c < _columns.size (); ++c)
{
headers.push_back (std::vector <std::string> ());
_columns[c]->renderHeader (headers[c], widths[c], _header);
// TODO Render column headers. if (headers[c].size () > max_lines)
max_lines = headers[c].size ();
}
// TODO Compose, render columns, in sequence. // for (int i = 0; i < headers.size (); ++i)
std::stringstream output; // for (int j = 0; j < headers[i].size (); ++j)
// std::cout << "# headers[" << i << "][" << j << "]=<" << headers[i][j] << ">\n";
// Output string.
std::string out;
// Render column headers.
std::string left_margin = std::string (_left_margin, ' ');
std::string extra = std::string (_extra_padding, ' ');
std::string intra = std::string (_intra_padding, ' ');
for (int i = 0; i < max_lines; ++i)
{
out += left_margin + extra;
for (int c = 0; c < _columns.size (); ++c)
{
if (c)
out += intra;
if (headers[i].size () < max_lines - i)
out += _header.colorize (std::string (widths[c], ' '));
else
out += headers[c][i];
}
out += extra + "\n";
}
// Compose, render columns, in sequence.
Color color ("cyan");
std::vector <std::vector <std::string> > cells;
std::vector <int>::iterator s; std::vector <int>::iterator s;
for (s = sequence.begin (); s != sequence.end (); ++s) for (s = sequence.begin (); s != sequence.end (); ++s)
{ {
// TODO render each row. max_lines = 0;
for (int c = 0; c < _columns.size (); ++c)
{
cells.push_back (std::vector <std::string> ());
_columns[c]->render (cells[c], data[*s], widths[c], color);
if (cells[c].size () > max_lines)
max_lines = cells[c].size ();
}
for (int i = 0; i < max_lines; ++i)
{
out += left_margin + extra;
for (int c = 0; c < _columns.size (); ++c)
{
if (c)
out += intra;
if (i < cells[c].size ())
out += cells[c][i];
else
out += color.colorize (std::string (widths[c], ' '));
}
out += extra + "\n";
}
cells.clear ();
} }
return output.str (); return out;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View file

@ -43,6 +43,7 @@ public:
void add (Column* column) { _columns.push_back (column); } void add (Column* column) { _columns.push_back (column); }
void width (int width) { _width = width; } void width (int width) { _width = width; }
void leftMargin (int margin) { _left_margin = margin; } void leftMargin (int margin) { _left_margin = margin; }
void colorHeader (Color& c) { _header = c; }
void colorOdd (Color& c) { _odd = c; } void colorOdd (Color& c) { _odd = c; }
void colorEven (Color& c) { _even = c; } void colorEven (Color& c) { _even = c; }
void intraPadding (int padding) { _intra_padding = padding; } void intraPadding (int padding) { _intra_padding = padding; }
@ -61,6 +62,7 @@ private:
std::vector <Column*> _columns; std::vector <Column*> _columns;
int _width; int _width;
int _left_margin; int _left_margin;
Color _header;
Color _odd; Color _odd;
Color _even; Color _even;
int _intra_padding; int _intra_padding;

View file

@ -25,9 +25,6 @@
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <iostream> // TODO Remove
#include <iomanip>
#include <sstream>
#include <math.h> #include <math.h>
#include <Context.h> #include <Context.h>
#include <ColID.h> #include <ColID.h>
@ -61,22 +58,19 @@ void ColumnID::measure (Task& task, int& minimum, int& maximum)
else length = (int) log10 ((double) task.id); // Slow else length = (int) log10 ((double) task.id); // Slow
minimum = maximum = length; minimum = maximum = length;
std::cout << "# ColID::measure id=" << task.id << " min=" << minimum << " max=" << maximum << "\n";
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void ColumnID::render (std::vector <std::string>& lines, Task& task, int width) void ColumnID::render (
std::vector <std::string>& lines,
Task& task,
int width,
Color& color)
{ {
std::stringstream line;
line << std::setw (width) << std::setfill (' ') << task.id;
if (task.id) if (task.id)
line << task.id; lines.push_back (color.colorize (rightJustify (task.id, width)));
else else
line << '-'; lines.push_back (color.colorize (rightJustify ("-", width)));
lines.push_back (line.str ());
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View file

@ -30,6 +30,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <Column.h> #include <Column.h>
#include <Color.h>
#include <Task.h> #include <Task.h>
class ColumnID : public Column class ColumnID : public Column
@ -39,7 +40,7 @@ public:
~ColumnID (); ~ColumnID ();
void measure (Task&, int&, int&); void measure (Task&, int&, int&);
void render (std::vector <std::string>&, Task&, int); void render (std::vector <std::string>&, Task&, int, Color&);
private: private:
}; };

View file

@ -25,10 +25,10 @@
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <iostream> // TODO Remove
#include <math.h>
#include <Context.h> #include <Context.h>
#include <Nibbler.h>
#include <ColProject.h> #include <ColProject.h>
#include <text.h>
extern Context context; extern Context context;
@ -50,36 +50,34 @@ ColumnProject::~ColumnProject ()
void ColumnProject::measure (Task& task, int& minimum, int& maximum) void ColumnProject::measure (Task& task, int& minimum, int& maximum)
{ {
std::string project = task.get ("project"); std::string project = task.get ("project");
minimum = maximum = project.length ();
std::string::size_type space = project.find (' ');
if (space == std::string::npos)
{
std::cout << "# ColProject::measure project=" << project << " min=" << minimum << " max=" << maximum << "\n";
return;
}
minimum = 0; minimum = 0;
int longest = 0; maximum = project.length ();
std::string::size_type last = -1;
while (space != std::string::npos) Nibbler nibbler (project);
std::string word;
while (nibbler.getUntilWS (word))
{ {
if (space - last - 1 > longest) nibbler.skipWS ();
longest = space - last - 1; if (word.length () > minimum)
minimum = word.length ();
last = space;
space = project.find (' ', last + 1);
} }
if (longest)
minimum = longest;
std::cout << "# ColProject::measure project=" << project << " min=" << minimum << " max=" << maximum << "\n";
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void ColumnProject::render (std::vector <std::string>& lines, Task& task, int width) void ColumnProject::render (
std::vector <std::string>& lines,
Task& task,
int width,
Color& color)
{ {
// TODO Can't use Nibbler here. Need to use a UTF8-safe version of extractLines.
Nibbler nibbler (task.get ("project"));
std::string word;
while (nibbler.getUntilWS (word))
{
nibbler.skipWS ();
lines.push_back (color.colorize (leftJustify (word, width)));
}
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View file

@ -30,6 +30,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <Column.h> #include <Column.h>
#include <Color.h>
#include <Task.h> #include <Task.h>
class ColumnProject : public Column class ColumnProject : public Column
@ -39,7 +40,7 @@ public:
~ColumnProject (); ~ColumnProject ();
void measure (Task&, int&, int&); void measure (Task&, int&, int&);
void render (std::vector <std::string>&, Task&, int); void render (std::vector <std::string>&, Task&, int, Color&);
private: private:
}; };

View file

@ -25,7 +25,6 @@
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <Context.h> #include <Context.h>
#include <Column.h> #include <Column.h>
#include <ColID.h> #include <ColID.h>
@ -87,27 +86,29 @@ Column::~Column ()
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void Column::renderHeader (std::vector <std::string>& lines, int width) void Column::renderHeader (
std::vector <std::string>& lines,
int width,
Color& color)
{ {
// Create a basic label. // Create a basic label.
std::string header; std::string header;
header.reserve (width); header.reserve (width);
header = _label; header = _label;
// Right pad with spaces, if necessary. // Create a fungible copy.
int length = characters (_label); Color c = color;
if (length < width)
_label += std::string (' ', width - length);
// Now underline the header, or add a dashed line. // Now underline the header, or add a dashed line.
if (context.config.getBoolean ("fontunderline")) if (context.config.getBoolean ("fontunderline"))
{ {
lines.push_back (header); c.blend (Color (Color::nocolor, Color::nocolor, true, false, false));
lines.push_back (c.colorize (leftJustify (header, width)));
} }
else else
{ {
lines.push_back (header); lines.push_back (c.colorize (leftJustify (header, width)));
lines.push_back (std::string ('-', width)); lines.push_back (c.colorize (std::string (width, '-')));
} }
} }

View file

@ -29,6 +29,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <Color.h>
#include <Task.h> #include <Task.h>
class Column class Column
@ -49,8 +50,8 @@ public:
std::string type () const { return _type; } std::string type () const { return _type; }
virtual void measure (Task&, int&, int&) = 0; virtual void measure (Task&, int&, int&) = 0;
virtual void renderHeader (std::vector <std::string>&, int); virtual void renderHeader (std::vector <std::string>&, int, Color&);
virtual void render (std::vector <std::string>&, Task&, int) = 0; virtual void render (std::vector <std::string>&, Task&, int, Color&) = 0;
protected: protected:
std::string _type; std::string _type;

View file

@ -40,6 +40,9 @@ int main (int argc, char** argv)
try try
{ {
// Set up configuration.
context.config.set ("fontunderline", true);
// Two sample tasks. // Two sample tasks.
Task t1 ("[project:\"Home\"]"); Task t1 ("[project:\"Home\"]");
t1.id = 1; t1.id = 1;
@ -55,14 +58,18 @@ int main (int argc, char** argv)
sequence.push_back (0); sequence.push_back (0);
sequence.push_back (1); sequence.push_back (1);
// Create colors.
Color header_color (Color (Color::yellow, Color::nocolor, false, false, false));
// Create a view. // Create a view.
View view; View view;
view.add (Column::factory ("id")); view.add (Column::factory ("id"));
view.add (Column::factory ("project")); view.add (Column::factory ("project"));
view.width (12); view.width (16);
view.leftMargin (0); view.leftMargin (4);
view.extraPadding (0); view.extraPadding (0);
view.intraPadding (1); view.intraPadding (1);
view.colorHeader (header_color);
// Render the view. // Render the view.
std::cout << view.render (data, sequence) std::cout << view.render (data, sequence)