diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f71bd9f5e..a6767c5ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,9 +18,9 @@ set (task_SRCS API.cpp API.h Att.cpp Att.h Cmd.cpp Cmd.h Color.cpp Color.h TransportCurl.h Tree.cpp Tree.h burndown.cpp command.cpp custom.cpp dependency.cpp diag.cpp edit.cpp export.cpp history.cpp i18n.h import.cpp interactive.cpp recur.cpp - report.cpp rules.cpp rx.cpp rx.h text.cpp text.h utf8.cpp utf8.h - util.cpp util.h Uri.cpp Uri.h Variant.cpp Variant.h View.cpp - View.h) + report.cpp rules.cpp rx.cpp rx.h sort.cpp text.cpp text.h + utf8.cpp utf8.h util.cpp util.h Uri.cpp Uri.h Variant.cpp + Variant.h View.cpp View.h) add_library (task STATIC ${task_SRCS}) add_executable (task_executable main.cpp) diff --git a/src/Context.cpp b/src/Context.cpp index 3b6fafc37..9aa7c1ebb 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -819,6 +819,31 @@ void Context::parse ( } } +//////////////////////////////////////////////////////////////////////////////// +void Context::decomposeSortField ( + const std::string& field, + std::string& key, + bool& ascending) +{ + int length = field.length (); + + if (field[length - 1] == '+') + { + ascending = true; + key = field.substr (0, length - 1); + } + else if (field[length - 1] == '-') + { + ascending = false; + key = field.substr (0, length - 1); + } + else + { + ascending = true; + key = field; + } +} + //////////////////////////////////////////////////////////////////////////////// // Note: The reason some of these are commented out is because the ::clear // method is not really "clear" but "clear_some". Some members do not need to diff --git a/src/Context.h b/src/Context.h index 8e52f85db..cf8139a7f 100644 --- a/src/Context.h +++ b/src/Context.h @@ -73,6 +73,7 @@ public: std::string canonicalize (const std::string&) const; void disallowModification () const; void applyOverrides (const std::vector &, std::vector &); + void decomposeSortField (const std::string&, std::string&, bool&); private: void loadCorrectConfigFile (); diff --git a/src/View.cpp b/src/View.cpp index 35f84e1d6..8b0a4a02d 100644 --- a/src/View.cpp +++ b/src/View.cpp @@ -211,7 +211,7 @@ std::string View::render (std::vector & data, std::vector & sequence) // Apply color rules to task. Color rule_color; - autoColorize (data[s], rule_color); + autoColorize (data[sequence[s]], rule_color); // Alternate rows based on |s % 2| bool odd = (s % 2) ? true : false; @@ -221,7 +221,7 @@ std::string View::render (std::vector & data, std::vector & sequence) for (int c = 0; c < _columns.size (); ++c) { cells.push_back (std::vector ()); - _columns[c]->render (cells[c], data[s], widths[c], row_color); + _columns[c]->render (cells[c], data[sequence[s]], widths[c], row_color); if (cells[c].size () > max_lines) max_lines = cells[c].size (); diff --git a/src/main.h b/src/main.h index 58b05deef..4c7eaa811 100644 --- a/src/main.h +++ b/src/main.h @@ -161,6 +161,9 @@ std::string taskInfoDifferences (const Task&, const Task&); std::string renderAttribute (const std::string&, const std::string&); std::string feedback (const Task&, const Task&); +// sort.cpp +void sort_tasks (std::vector &, std::vector &, const std::string&); + // list template /////////////////////////////////////////////////////////////////////////////// template bool listDiff (const T& left, const T& right) diff --git a/src/sort.cpp b/src/sort.cpp index 77c2d116a..37eb6a6be 100644 --- a/src/sort.cpp +++ b/src/sort.cpp @@ -25,211 +25,172 @@ // //////////////////////////////////////////////////////////////////////////////// +#include #include #include +#include +#include #include +#include -static std::vector * data = NULL; +extern Context context; + +static std::vector * global_data = NULL; +static std::vector global_keys; +static bool sort_compare (int, int); //////////////////////////////////////////////////////////////////////////////// -void view_sort ( - std::vector & data) +void sort_tasks ( + std::vector & data, + std::vector & order, + const std::string& keys) { + global_data = &data; + + // Split the key defs. + global_keys.clear (); + split (global_keys, keys, ','); + + // Only sort if necessary. + if (order.size ()) + std::stable_sort (order.begin (), order.end (), sort_compare); } //////////////////////////////////////////////////////////////////////////////// // Re-implementation, using direct Task access instead of data copies that // require re-parsing. -bool sort_compare (int left, int right) -{ -} - -//////////////////////////////////////////////////////////////////////////////// +// // Essentially a static implementation of a dynamic operator<. -bool sort_compare (int left, int right) +static bool sort_compare (int left, int right) { - for (size_t c = 0; c < table->mSortColumns.size (); ++c) + int result; + std::string field; + bool ascending; + + int left_number; + int right_number; + std::string left_string; + std::string right_string; + time_t left_date; + time_t right_date; + float left_real; + float right_real; + + std::vector ::iterator k; + for (k = global_keys.begin (); k != global_keys.end (); ++k) { - int column = table->mSortColumns[c]; - Table::order sort_type = table->mSortOrder[column]; + context.decomposeSortField (*k, field, ascending); - 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) + // Number. + if (field == "id") { - switch (sort_type) - { - case Table::ascendingNumeric: - return (float)*cell_left < (float)*cell_right ? true : false; - break; + left_number = (*global_data)[left].id; + right_number = (*global_data)[right].id; - case Table::descendingNumeric: - return (float)*cell_left < (float)*cell_right ? false : true; - break; + if (left_number == right_number) + continue; - case Table::ascendingCharacter: - return (std::string)*cell_left < (std::string)*cell_right ? true : false; - break; + if (ascending) + return left_number < right_number; - case Table::descendingCharacter: - return (std::string)*cell_left < (std::string)*cell_right ? false : true; - break; + return left_number > right_number; + } - case Table::ascendingDate: - { - // something > nothing. - if ((std::string)*cell_left != "" && (std::string)*cell_right == "") - return false; + // String. + else if (field == "description" || + field == "depends" || + field == "project" || + field == "status" || + field == "tags" || + field == "uuid") + { + left_string = (*global_data)[left].get (field); + right_string = (*global_data)[right].get (field); - // nothing < something. - else if ((std::string)*cell_left == "" && (std::string)*cell_right != "") - return true; + if (left_string == right_string) + continue; - else - { - Date dl ((std::string)*cell_left, table->mDateFormat); - Date dr ((std::string)*cell_right, table->mDateFormat); - return dl < dr ? true : false; - } - } - break; + if (ascending) + return left_string < right_string; - case Table::descendingDate: - { - // something > nothing. - if ((std::string)*cell_left != "" && (std::string)*cell_right == "") - return true; + return left_string > right_string; + } - // nothing < something. - else if ((std::string)*cell_left == "" && (std::string)*cell_right != "") - return false; + // Priority. + else if (field == "priority") + { + left_string = (*global_data)[left].get (field); + right_string = (*global_data)[right].get (field); + if (left_string == right_string) + continue; - else - { - Date dl ((std::string)*cell_left, table->mDateFormat); - Date dr ((std::string)*cell_right, table->mDateFormat); - return dl < dr ? false : true; - } - } - break; + if (ascending) + return (left_string == "" && right_string != "") || + (left_string == "L" && (right_string == "M" || right_string == "H")) || + (left_string == "M" && right_string == "H"); - case Table::ascendingDueDate: - { - // something > nothing. - if ((std::string)*cell_left != "" && (std::string)*cell_right == "") - return true; + return (left_string != "" && right_string == "") || + (left_string == "M" && right_string == "L") || + (left_string == "H" && (right_string == "M" || right_string == "L")); + } - // nothing < something. - else if ((std::string)*cell_left == "" && (std::string)*cell_right != "") - return false; + // Date. + else if (field == "due" || + field == "end" || + field == "entry" || + field == "start" || + field == "until" || + field == "wait") + { + left_string = (*global_data)[left].get (field); + right_string = (*global_data)[right].get (field); - 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"); + if (left_string == right_string) + continue; - Date dl ((std::string)*cell_left, format); - Date dr ((std::string)*cell_right, format); - return dl < dr ? true : false; - } - } - break; + left_date = atoi (left_string.c_str ()); + right_date = atoi (right_string.c_str ()); - case Table::descendingDueDate: - { - // something > nothing. - if ((std::string)*cell_left != "" && (std::string)*cell_right == "") - return true; + if (ascending) + return left_date < right_date; - // nothing < something. - else if ((std::string)*cell_left == "" && (std::string)*cell_right != "") - return false; + return left_date > right_date; + } - 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"); + // Duration. + else if (field == "recur") + { + left_string = (*global_data)[left].get (field); + right_string = (*global_data)[right].get (field); - Date dl ((std::string)*cell_left, format); - Date dr ((std::string)*cell_right, format); - return dl < dr ? false : true; - } - } - break; + if (left_string == right_string) + continue; - 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; + Duration left_duration (left_string); + Duration right_duration (right_string); + if (ascending) + return left_duration < right_duration; - 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; + return left_duration > right_duration; + } - 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; + // Urgency. + else if (field == "urgency") + { + left_real = (*global_data)[left].urgency (); + right_real = (*global_data)[right].urgency (); - 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; - } + if (left_real == right_real) + continue; + + if (ascending) + return left_real < right_real; + + return left_real > right_real; } } return false; } + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/text.cpp b/src/text.cpp index be5d16fb7..6552738bc 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -220,7 +220,7 @@ int longestWord (const std::string& input) { int longest = 0; int length = 0; - std::string::size_type i; + std::string::size_type i = 0; int character; while (character = utf8_next_char (input, i)) diff --git a/test/view.t.cpp b/test/view.t.cpp index 98ae8ac0e..972542552 100644 --- a/test/view.t.cpp +++ b/test/view.t.cpp @@ -30,6 +30,7 @@ #include #include #include +#include Context context; @@ -71,15 +72,26 @@ int main (int argc, char** argv) "depends:\"2a64f6e0-bf8e-430d-bf71-9ec3f0d9b661\"" "]"); t2.id = 11; + Task t3 ("[" + "status:\"pending\" " + "uuid:\"c44cb9c3-3fc0-483f-bfb2-3bf134f05554\" " + "description:\"Another description\" " + "project:\"Garden\" " + "]"); + t3.id = 8; std::vector data; data.push_back (t1); data.push_back (t2); + data.push_back (t3); // Sequence of tasks. std::vector sequence; sequence.push_back (0); sequence.push_back (1); + sequence.push_back (2); + + sort_tasks (data, sequence, "description+,id-"); // Create colors. Color header_color (Color (Color::yellow, Color::nocolor, false, false, false)); @@ -95,10 +107,10 @@ int main (int argc, char** argv) view.add (Column::factory ("tags")); // view.add (Column::factory ("tags.indicator")); view.add (Column::factory ("tags.count")); -// view.add (Column::factory ("description")); + view.add (Column::factory ("description")); // view.add (Column::factory ("description.desc")); // view.add (Column::factory ("description.truncated")); - view.add (Column::factory ("description.oneline")); +// view.add (Column::factory ("description.oneline")); // view.add (Column::factory ("description.count")); // view.add (Column::factory ("depends")); // view.add (Column::factory ("depends.count")); @@ -134,7 +146,7 @@ int main (int argc, char** argv) // Render the view. std::cout << view.render (data, sequence); - t.is (view.lines (), 3, "View::lines == 3"); + t.is (view.lines (), 5, "View::lines == 5"); } catch (std::string& e)