mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00

- Corrected sorting to use std::stable_sort instead of std::sort, which is not guaranteed stable (thanks to Stefan Hacker).
906 lines
26 KiB
C++
906 lines
26 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
// taskwarrior - a command line task list manager.
|
|
//
|
|
// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez.
|
|
// All rights reserved.
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify it under
|
|
// the terms of the GNU General Public License as published by the Free Software
|
|
// Foundation; either version 2 of the License, or (at your option) any later
|
|
// version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
// details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program; if not, write to the
|
|
//
|
|
// Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor,
|
|
// Boston, MA
|
|
// 02110-1301
|
|
// USA
|
|
//
|
|
//
|
|
//
|
|
// Attributes Table Row Column Cell
|
|
// ----------------------------------------------------
|
|
// foreground color Y Y Y Y
|
|
// background color Y Y Y Y
|
|
// padding Y Y Y Y
|
|
// wrap Y Y Y Y
|
|
// width Y - Y Y
|
|
// height - Y - Y
|
|
// justification Y Y Y Y
|
|
//
|
|
// Precedence
|
|
// If attributes conflict, the precedence order is:
|
|
// cell
|
|
// row
|
|
// column
|
|
// table
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
#include <iostream>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <algorithm>
|
|
#include "Table.h"
|
|
#include "Date.h"
|
|
#include "Duration.h"
|
|
#include "Timer.h"
|
|
#include "text.h"
|
|
#include "util.h"
|
|
#include "Context.h"
|
|
|
|
extern Context context;
|
|
Table* table = NULL;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
Table::Table ()
|
|
: mRows (0)
|
|
, mIntraPadding (1)
|
|
, mDashedUnderline (false)
|
|
, mTablePadding (0)
|
|
, mTableWidth (0)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
Table::~Table ()
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setTableAlternateColor (const Color& c)
|
|
{
|
|
alternate = c;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setTablePadding (int padding)
|
|
{
|
|
mTablePadding = padding;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setTableIntraPadding (int padding)
|
|
{
|
|
mIntraPadding = padding;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setTableWidth (int width)
|
|
{
|
|
assert (width > 0);
|
|
mTableWidth = width;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setTableDashedUnderline ()
|
|
{
|
|
mDashedUnderline = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int Table::addColumn (const std::string& col)
|
|
{
|
|
mSpecifiedWidth.push_back (minimum);
|
|
mMaxDataWidth.push_back (col == "" ? 1 : characters (col));
|
|
mCalculatedWidth.push_back (0);
|
|
mColumnPadding.push_back (0);
|
|
|
|
mColumns.push_back (col == "" ? " " : col);
|
|
return mColumns.size () - 1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setColumnUnderline (int column)
|
|
{
|
|
char id[12];
|
|
sprintf (id, "col:%d", column);
|
|
mUnderline[id] = Color (Color::nocolor, Color::nocolor, true, false, false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setColumnPadding (int column, int padding)
|
|
{
|
|
mColumnPadding[column] = padding;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setColumnWidth (int column, int width)
|
|
{
|
|
assert (width > 0);
|
|
mSpecifiedWidth[column] = width;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setColumnWidth (int column, sizing s)
|
|
{
|
|
mSpecifiedWidth[column] = (int) s;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setColumnCommify (int column)
|
|
{
|
|
mCommify[column] = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setColumnJustification (int column, just j)
|
|
{
|
|
mJustification[column] = j;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::sortOn (int column, order o)
|
|
{
|
|
mSortColumns.push_back (column);
|
|
mSortOrder[column] = o;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int Table::addRow ()
|
|
{
|
|
return mRows++;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setRowColor (const int row, const Color& c)
|
|
{
|
|
char id[12];
|
|
sprintf (id, "row:%d", row);
|
|
mColor[id].blend (c);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::addCell (const int row, const int col, const std::string& data)
|
|
{
|
|
int length = 0;
|
|
|
|
if (mCommify.find (col) != mCommify.end ())
|
|
mData.add (row, col, commify (data));
|
|
else
|
|
mData.add (row, col, data);
|
|
|
|
// For multi-line cells, find the longest line.
|
|
if (data.find ("\n") != std::string::npos)
|
|
{
|
|
length = 0;
|
|
std::vector <std::string> lines;
|
|
split (lines, data, "\n");
|
|
for (unsigned int i = 0; i < lines.size (); ++i)
|
|
if (characters (lines[i]) > length)
|
|
length = characters (lines[i]);
|
|
}
|
|
else
|
|
length = characters (data);
|
|
|
|
// Automatically maintain max width.
|
|
mMaxDataWidth[col] = max (mMaxDataWidth[col], length);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::addCell (const int row, const int col, const char data)
|
|
{
|
|
mData.add (row, col, data);
|
|
|
|
// Automatically maintain max width.
|
|
mMaxDataWidth[col] = max (mMaxDataWidth[col], 1);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::addCell (const int row, const int col, const int data)
|
|
{
|
|
char value[12];
|
|
sprintf (value, "%d", data);
|
|
|
|
if (mCommify.find (col) != mCommify.end ())
|
|
mData.add (row, col, commify (value));
|
|
else
|
|
mData.add (row, col, value);
|
|
|
|
// Automatically maintain max width.
|
|
mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) strlen (value));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::addCell (const int row, const int col, const float data)
|
|
{
|
|
char value[24];
|
|
sprintf (value, "%.2f", data);
|
|
|
|
if (mCommify.find (col) != mCommify.end ())
|
|
mData.add (row, col, commify (value));
|
|
else
|
|
mData.add (row, col, value);
|
|
|
|
// Automatically maintain max width.
|
|
mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) strlen (value));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::addCell (const int row, const int col, const double data)
|
|
{
|
|
char value[24];
|
|
sprintf (value, "%.6f", data);
|
|
|
|
if (mCommify.find (col) != mCommify.end ())
|
|
mData.add (row, col, commify (value));
|
|
else
|
|
mData.add (row, col, value);
|
|
|
|
// Automatically maintain max width.
|
|
mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) strlen (value));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setCellColor (const int row, const int col, const Color& c)
|
|
{
|
|
char id[24];
|
|
sprintf (id, "cell:%d,%d", row, col);
|
|
mColor[id] = c;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
std::string Table::getCell (const int row, const int col)
|
|
{
|
|
Grid::Cell* c = mData.byRow (row, col);
|
|
if (c)
|
|
return (std::string) *c;
|
|
|
|
return "";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
Color Table::getColor (const int index, const int row, const int col)
|
|
{
|
|
// Color defaults to trivial.
|
|
Color c;
|
|
|
|
// For alternating rows, use Table::alternate.
|
|
std::map <std::string, Color>::iterator i;
|
|
char id[24];
|
|
|
|
if (index % 2)
|
|
c = alternate;
|
|
|
|
/*
|
|
// TODO Obsolete - this is not used. Consider removal.
|
|
// Blend with a table color, if specified.
|
|
if ((i = mColor.find ("table")) != mColor.end ())
|
|
c.blend (i->second);
|
|
|
|
// Blend with a column color, if specified.
|
|
sprintf (id, "col:%d", col);
|
|
if ((i = mColor.find (id)) != mColor.end ())
|
|
c.blend (i->second);
|
|
*/
|
|
|
|
// Blend with a row color, if specified.
|
|
sprintf (id, "row:%d", row);
|
|
if ((i = mColor.find (id)) != mColor.end ())
|
|
c.blend (i->second);
|
|
|
|
// Blend with a cell color, if specified.
|
|
sprintf (id, "cell:%d,%d", row, col);
|
|
if ((i = mColor.find (id)) != mColor.end ())
|
|
c.blend (i->second);
|
|
|
|
return c;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
Color Table::getHeaderUnderline (int col)
|
|
{
|
|
char idCol[12];
|
|
sprintf (idCol, "col:%d", col);
|
|
|
|
return mUnderline.find (idCol) != mUnderline.end () ? mUnderline[idCol]
|
|
: Color ();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int Table::getIntraPadding ()
|
|
{
|
|
return mIntraPadding;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int Table::getPadding (int col)
|
|
{
|
|
return max (mColumnPadding[col], mTablePadding);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Using mSpecifiedWidth, mMaxDataWidth, generate mCalculatedWidth.
|
|
void Table::calculateColumnWidths ()
|
|
{
|
|
// Ideal case: either no table width is specified, or everything fits without
|
|
// wrapping into mTableWidth.
|
|
std::vector <int> ideal = mMaxDataWidth;
|
|
int width = 0;
|
|
int countFlexible = 0;
|
|
for (size_t c = 0; c < mColumns.size (); ++c)
|
|
{
|
|
if (mSpecifiedWidth[c] == flexible)
|
|
++countFlexible;
|
|
|
|
else if (mSpecifiedWidth[c] > 0)
|
|
ideal[c] = mSpecifiedWidth[c];
|
|
|
|
width += mColumnPadding[c] +
|
|
ideal[c] +
|
|
mColumnPadding[c] +
|
|
(c > 0 ? mIntraPadding : 0);
|
|
}
|
|
|
|
if (!mTableWidth || width < mTableWidth)
|
|
{
|
|
mCalculatedWidth = ideal;
|
|
return;
|
|
}
|
|
|
|
// Try again, with available space divided among the flexible columns.
|
|
if (countFlexible)
|
|
{
|
|
ideal = mMaxDataWidth;
|
|
width = 0;
|
|
for (size_t c = 0; c < mColumns.size (); ++c)
|
|
{
|
|
if (mSpecifiedWidth[c] > 0)
|
|
ideal[c] = mSpecifiedWidth[c];
|
|
else if (mSpecifiedWidth[c] == flexible)
|
|
{
|
|
ideal[c] = 0;
|
|
}
|
|
|
|
width += mColumnPadding[c] +
|
|
ideal[c] +
|
|
mColumnPadding[c] +
|
|
(c > 0 ? mIntraPadding : 0);
|
|
}
|
|
|
|
int available = mTableWidth - width;
|
|
if (width < mTableWidth) // if there is room to wiggle in
|
|
{
|
|
int shared = available / countFlexible;
|
|
int remainder = available % countFlexible;
|
|
|
|
int lastFlexible = mColumns.size () - 1;
|
|
for (size_t c = 0; c < mColumns.size (); ++c)
|
|
{
|
|
if (mSpecifiedWidth[c] == flexible)
|
|
{
|
|
lastFlexible = c;
|
|
ideal[c] += shared;
|
|
}
|
|
}
|
|
|
|
// Remainder goes to last column.
|
|
ideal[lastFlexible] += remainder;
|
|
mCalculatedWidth = ideal;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// The fallback position is to assume no width was specified, and just
|
|
// calculate widths accordingly.
|
|
mTableWidth = 0;
|
|
calculateColumnWidths ();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Try again, treating minimum columns as flexible.
|
|
// std::cout << "# no flexible columns. Now what?\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
Table::just Table::getJustification (const int row, const int col)
|
|
{
|
|
if (mJustification.find (col) != mJustification.end ())
|
|
return mJustification[col];
|
|
|
|
return left;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
Table::just Table::getHeaderJustification (int col)
|
|
{
|
|
return mJustification.find (col) != mJustification.end () ? mJustification[col]
|
|
: left;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// data One Data to be rendered
|
|
// width 8 Max data width for column/specified width
|
|
// padding 1 Extra padding around data
|
|
// intraPadding 0 Extra padding between columns only
|
|
// justification right Alignment withing padding
|
|
//
|
|
// Returns:
|
|
// " One "
|
|
// One data
|
|
// ^ ^ padding
|
|
// ^ intraPadding
|
|
// ^^^^^^^^ width
|
|
// ^ ^ fg/bg
|
|
//
|
|
const std::string Table::formatHeader (
|
|
int col,
|
|
int width,
|
|
int padding)
|
|
{
|
|
assert (width > 0);
|
|
|
|
std::string data = mColumns[col];
|
|
Color c = getHeaderUnderline (col);
|
|
int gap = width - strippedLength (data); // TODO Does this need characters () too?
|
|
|
|
std::string pad = std::string (padding, ' ');
|
|
|
|
// TODO When the following is replaced by:
|
|
// std::string postJust = std::string (gap, ' ');
|
|
// two unit tests fail.
|
|
std::string postJust = "";
|
|
for (int i = 0; i < gap; ++i)
|
|
postJust += " ";
|
|
|
|
std::string intraPad = "";
|
|
if (col < (signed) mColumns.size () - 1)
|
|
intraPad = std::string (getIntraPadding (), ' ');
|
|
|
|
return c.colorize (pad + data + postJust + pad) + intraPad;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// data One Data to be rendered
|
|
// width 8 Max data width for column/specified width
|
|
// padding 1 Extra padding around data
|
|
// intraPadding 0 Extra padding between columns only
|
|
// justification right Alignment withing padding
|
|
//
|
|
// Returns:
|
|
// "------- "
|
|
// ------- data
|
|
// ^ ^ padding
|
|
// ^ intraPadding
|
|
// ^^^^^^^^ width
|
|
// ^ ^ fg/bg
|
|
//
|
|
const std::string Table::formatHeaderDashedUnderline (
|
|
int col,
|
|
int width,
|
|
int padding)
|
|
{
|
|
assert (width > 0);
|
|
|
|
Color c = getHeaderUnderline (col);
|
|
|
|
std::string data = std::string (width, '-');
|
|
std::string pad = std::string (padding, ' ');
|
|
std::string intraPad = "";
|
|
|
|
// Place the value within the available space - justify.
|
|
if (col < (signed) mColumns.size () - 1)
|
|
intraPad = std::string (getIntraPadding (), ' ');
|
|
|
|
return c.colorize (pad + data + pad) + intraPad;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::formatCell (
|
|
const int index,
|
|
const int row,
|
|
const int col,
|
|
const int width,
|
|
const int padding,
|
|
std::vector <std::string>& lines,
|
|
std::string& blank)
|
|
{
|
|
assert (width > 0);
|
|
|
|
Color c = getColor (index, row, col);
|
|
just justification = getJustification (row, col);
|
|
std::string data = getCell (row, col);
|
|
|
|
std::string pad = std::string (padding, ' ');
|
|
std::string intraPad = "";
|
|
|
|
if (col < (signed) mColumns.size () - 1)
|
|
intraPad = std::string (getIntraPadding (), ' ');
|
|
|
|
// Break the text into chunks of width characters.
|
|
std::string preJust;
|
|
std::string postJust;
|
|
std::vector <std::string> chunks;
|
|
wrapText (chunks, data, width);
|
|
for (size_t chunk = 0; chunk < chunks.size (); ++chunk)
|
|
{
|
|
// Place the data within the available space - justify.
|
|
int gap = width - characters (chunks[chunk]);
|
|
|
|
preJust = "";
|
|
postJust = "";
|
|
|
|
if (justification == left)
|
|
postJust = std::string (gap, ' ');
|
|
|
|
else if (justification == right)
|
|
preJust = std::string (gap, ' ');
|
|
|
|
else if (justification == center)
|
|
{
|
|
preJust = std::string (gap / 2, ' ');
|
|
postJust = std::string (gap - preJust.length (), ' ');
|
|
}
|
|
|
|
lines.push_back (c.colorize (pad + preJust + chunks[chunk] + postJust + pad + intraPad));
|
|
}
|
|
|
|
// The blank is used to vertically pad cells that have blank lines.
|
|
pad = std::string (width, ' ');
|
|
|
|
blank = c.colorize (pad + intraPad);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setDateFormat (const std::string& dateFormat)
|
|
{
|
|
mDateFormat = dateFormat;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Table::setReportName (const std::string& reportName)
|
|
{
|
|
mReportName = reportName;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int Table::rowCount ()
|
|
{
|
|
return mRows;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int Table::columnCount ()
|
|
{
|
|
return mColumns.size ();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Essentially a static implementation of a dynamic operator<.
|
|
bool sort_compare (int left, int right)
|
|
{
|
|
for (size_t c = 0; c < table->mSortColumns.size (); ++c)
|
|
{
|
|
int column = table->mSortColumns[c];
|
|
Table::order sort_type = table->mSortOrder[column];
|
|
|
|
Grid::Cell* cell_left = table->mData.byRow (left, column);
|
|
Grid::Cell* cell_right = table->mData.byRow (right, column);
|
|
|
|
// Equally NULL - next column.
|
|
if (cell_left == NULL && cell_right == NULL)
|
|
continue;
|
|
|
|
// Equal - next column
|
|
if (cell_left && cell_right && *cell_left == *cell_right)
|
|
continue;
|
|
|
|
// Note: Table::ascendingDueDate is not represented here because it is not
|
|
// possible to have a NULL due date, only a blank "".
|
|
|
|
// nothing < something.
|
|
if (cell_left == NULL && cell_right != NULL)
|
|
return (sort_type == Table::ascendingNumeric ||
|
|
sort_type == Table::ascendingCharacter ||
|
|
sort_type == Table::ascendingPriority ||
|
|
sort_type == Table::ascendingDate ||
|
|
sort_type == Table::ascendingPeriod) ? true : false;
|
|
|
|
// something > nothing.
|
|
if (cell_left != NULL && cell_right == NULL)
|
|
return (sort_type == Table::ascendingNumeric ||
|
|
sort_type == Table::ascendingCharacter ||
|
|
sort_type == Table::ascendingPriority ||
|
|
sort_type == Table::ascendingDate ||
|
|
sort_type == Table::ascendingPeriod) ? false : true;
|
|
|
|
// Differing data - do a proper comparison.
|
|
if (cell_left && cell_right)
|
|
{
|
|
switch (sort_type)
|
|
{
|
|
case Table::ascendingNumeric:
|
|
return (float)*cell_left < (float)*cell_right ? true : false;
|
|
break;
|
|
|
|
case Table::descendingNumeric:
|
|
return (float)*cell_left < (float)*cell_right ? false : true;
|
|
break;
|
|
|
|
case Table::ascendingCharacter:
|
|
return (std::string)*cell_left < (std::string)*cell_right ? true : false;
|
|
break;
|
|
|
|
case Table::descendingCharacter:
|
|
return (std::string)*cell_left < (std::string)*cell_right ? false : true;
|
|
break;
|
|
|
|
case Table::ascendingDate:
|
|
{
|
|
// something > nothing.
|
|
if ((std::string)*cell_left != "" && (std::string)*cell_right == "")
|
|
return false;
|
|
|
|
// nothing < something.
|
|
else if ((std::string)*cell_left == "" && (std::string)*cell_right != "")
|
|
return true;
|
|
|
|
else
|
|
{
|
|
Date dl ((std::string)*cell_left, table->mDateFormat);
|
|
Date dr ((std::string)*cell_right, table->mDateFormat);
|
|
return dl < dr ? true : false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Table::descendingDate:
|
|
{
|
|
// something > nothing.
|
|
if ((std::string)*cell_left != "" && (std::string)*cell_right == "")
|
|
return true;
|
|
|
|
// nothing < something.
|
|
else if ((std::string)*cell_left == "" && (std::string)*cell_right != "")
|
|
return false;
|
|
|
|
else
|
|
{
|
|
Date dl ((std::string)*cell_left, table->mDateFormat);
|
|
Date dr ((std::string)*cell_right, table->mDateFormat);
|
|
return dl < dr ? false : true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Table::ascendingDueDate:
|
|
{
|
|
// something > nothing.
|
|
if ((std::string)*cell_left != "" && (std::string)*cell_right == "")
|
|
return true;
|
|
|
|
// nothing < something.
|
|
else if ((std::string)*cell_left == "" && (std::string)*cell_right != "")
|
|
return false;
|
|
|
|
else
|
|
{
|
|
std::string format = context.config.get ("report." + table->mReportName + ".dateformat");
|
|
if (format == "")
|
|
format = context.config.get ("dateformat.report");
|
|
if (format == "")
|
|
format = context.config.get ("dateformat");
|
|
|
|
Date dl ((std::string)*cell_left, format);
|
|
Date dr ((std::string)*cell_right, format);
|
|
return dl < dr ? true : false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Table::descendingDueDate:
|
|
{
|
|
// something > nothing.
|
|
if ((std::string)*cell_left != "" && (std::string)*cell_right == "")
|
|
return true;
|
|
|
|
// nothing < something.
|
|
else if ((std::string)*cell_left == "" && (std::string)*cell_right != "")
|
|
return false;
|
|
|
|
else
|
|
{
|
|
std::string format = context.config.get ("report." + table->mReportName + ".dateformat");
|
|
if (format == "")
|
|
format = context.config.get ("dateformat.report");
|
|
if (format == "")
|
|
format = context.config.get ("dateformat");
|
|
|
|
Date dl ((std::string)*cell_left, format);
|
|
Date dr ((std::string)*cell_right, format);
|
|
return dl < dr ? false : true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Table::ascendingPriority:
|
|
if (((std::string)*cell_left == "" && (std::string)*cell_right != "") ||
|
|
((std::string)*cell_left == "L" && ((std::string)*cell_right == "M" || (std::string)*cell_right == "H")) ||
|
|
((std::string)*cell_left == "M" && (std::string)*cell_right == "H"))
|
|
return true;
|
|
else
|
|
return false;
|
|
break;
|
|
|
|
case Table::descendingPriority:
|
|
if (((std::string)*cell_left != "" && (std::string)*cell_right == "") ||
|
|
((std::string)*cell_left == "M" && (std::string)*cell_right == "L") ||
|
|
((std::string)*cell_left == "H" && ((std::string)*cell_right == "L" || (std::string)*cell_right == "M")))
|
|
return true;
|
|
else
|
|
return false;
|
|
break;
|
|
|
|
case Table::ascendingPeriod:
|
|
if ((std::string)*cell_left == "" && (std::string)*cell_right != "")
|
|
return true;
|
|
else if ((std::string)*cell_left != "" && (std::string)*cell_right == "")
|
|
return false;
|
|
else
|
|
return Duration ((std::string)*cell_left) < Duration ((std::string)*cell_right) ? true : false;
|
|
break;
|
|
|
|
case Table::descendingPeriod:
|
|
if ((std::string)*cell_left != "" && (std::string)*cell_right == "")
|
|
return false;
|
|
else if ((std::string)*cell_left == "" && (std::string)*cell_right != "")
|
|
return true;
|
|
else
|
|
return Duration ((std::string)*cell_left) < Duration ((std::string)*cell_right) ? false : true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
const std::string Table::render (int maxrows /* = 0 */, int maxlines /* = 0 */)
|
|
{
|
|
Timer t ("Table::render");
|
|
|
|
// May not exceed maxlines, if non-zero.
|
|
int renderedlines = 0;
|
|
|
|
calculateColumnWidths ();
|
|
|
|
// Print column headers in column order.
|
|
std::string output;
|
|
std::string underline;
|
|
for (size_t col = 0; col < mColumns.size (); ++col)
|
|
{
|
|
output += formatHeader (
|
|
col,
|
|
mCalculatedWidth[col],
|
|
mColumnPadding[col]);
|
|
|
|
if (mDashedUnderline)
|
|
underline += formatHeaderDashedUnderline (
|
|
col,
|
|
mCalculatedWidth[col],
|
|
mColumnPadding[col]);
|
|
}
|
|
|
|
output.erase (output.find_last_not_of (" ") + 1);
|
|
output += "\n";
|
|
++renderedlines;
|
|
if (underline.length ())
|
|
{
|
|
output += underline + "\n";
|
|
++renderedlines;
|
|
}
|
|
|
|
// Determine row order, according to sort options.
|
|
std::vector <int> order;
|
|
{
|
|
Timer t ("Table::render/sort");
|
|
for (int row = 0; row < mRows; ++row)
|
|
order.push_back (row);
|
|
|
|
// Only sort if necessary.
|
|
if (mSortColumns.size ())
|
|
{
|
|
table = this; // Substitute for 'this' in the static 'sort_compare'.
|
|
std::stable_sort (order.begin (), order.end (), sort_compare);
|
|
}
|
|
}
|
|
|
|
// If a non-zero maxrows is specified, then it limits the number of rows of
|
|
// the table that are rendered.
|
|
int limitrows = mRows;
|
|
if (maxrows != 0)
|
|
limitrows = min (maxrows, mRows);
|
|
|
|
// If a non-zero maxlines is specified, then it limits the number of lines
|
|
// of output from the table that are rendered.
|
|
|
|
// Print all rows.
|
|
Timer t2 ("Table::render/compose");
|
|
for (int row = 0; row < limitrows; ++row)
|
|
{
|
|
std::vector <std::vector <std::string> > columns;
|
|
std::vector <std::string> blanks;
|
|
|
|
size_t maxHeight = 0;
|
|
for (size_t col = 0; col < mColumns.size (); ++col)
|
|
{
|
|
std::vector <std::string> lines;
|
|
std::string blank;
|
|
formatCell (
|
|
row,
|
|
order[row],
|
|
col,
|
|
mCalculatedWidth[col],
|
|
mColumnPadding[col],
|
|
lines,
|
|
blank);
|
|
|
|
columns.push_back (lines);
|
|
blanks.push_back (blank);
|
|
|
|
maxHeight = max (maxHeight, columns[col].size ());
|
|
}
|
|
|
|
if (maxHeight)
|
|
{
|
|
for (size_t lines = 0; lines < maxHeight; ++lines)
|
|
{
|
|
for (size_t col = 0; col < mColumns.size (); ++col)
|
|
if (lines < columns[col].size ())
|
|
output += columns[col][lines];
|
|
else
|
|
output += blanks[col];
|
|
|
|
// Trim right.
|
|
output.erase (output.find_last_not_of (" ") + 1);
|
|
output += "\n";
|
|
|
|
++renderedlines;
|
|
|
|
if (maxlines != 0 && renderedlines >= maxlines)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Trim right.
|
|
output.erase (output.find_last_not_of (" ") + 1);
|
|
output += "\n";
|
|
}
|
|
|
|
if (maxlines != 0 && renderedlines >= maxlines)
|
|
break;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|