diff --git a/src/Att.cpp b/src/Att.cpp index 3208c7a2d..c3b1734ba 100644 --- a/src/Att.cpp +++ b/src/Att.cpp @@ -62,6 +62,7 @@ static const char* modifiableNames[] = "due", "recur", "until", + "wait", }; // Synonyms on the same line. @@ -324,8 +325,9 @@ bool Att::validNameValue ( Text::guessColor (value); } - else if (name == "due" || - name == "until") + else if (name == "due" || + name == "until" || + name == "wait") { // Validate and convert to epoch. if (value != "") @@ -356,6 +358,7 @@ bool Att::validNameValue ( candidates.push_back ("completed"); candidates.push_back ("deleted"); candidates.push_back ("recurring"); + candidates.push_back ("waiting"); autoComplete (value, candidates, matches); if (matches.size () == 1) @@ -363,7 +366,7 @@ bool Att::validNameValue ( else throw std::string ("\"") + value + - "\" is not a valid status. Use 'pending', 'completed', 'deleted' or 'recurring'."; + "\" is not a valid status. Use 'pending', 'completed', 'deleted', 'recurring' or 'waiting'."; } else if (! validInternalName (name) && @@ -388,11 +391,12 @@ bool Att::validMod (const std::string& mod) // The type of an attribute is useful for modifier evaluation. std::string Att::type (const std::string& name) const { - if (name == "due" || + if (name == "due" || name == "until" || name == "start" || name == "entry" || - name == "end") + name == "end" || + name == "wait") return "date"; else if (name == "recur") diff --git a/src/Config.cpp b/src/Config.cpp index 08ebc9e32..b8106d65a 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -218,6 +218,12 @@ void Config::createDefault (const std::string& home) fprintf (out, "report.recurring.sort=due+,priority-,project+\n"); // TODO i18n fprintf (out, "report.recurring.filter=status:pending parent.any:\n"); // TODO i18n + fprintf (out, "report.waiting.description=Lists all waiting tasks matching the specified criteria\n"); // TODO i18n + fprintf (out, "report.waiting.columns=id,project,priority,wait,age,description\n"); // TODO i18n + fprintf (out, "report.waiting.labels=ID,Project,Pri,Wait,Age,Description\n"); // TODO i18n + fprintf (out, "report.waiting.sort=wait+,priority-,project+\n"); // TODO i18n + fprintf (out, "report.waiting.filter=status:waiting\n"); // TODO i18n + fclose (out); std::cout << "Done." << std::endl; // TODO i18n @@ -290,6 +296,12 @@ void Config::setDefaults () set ("report.recurring.labels", "ID,Project,Pri,Due,Recur,Active,Age,Description"); // TODO i18n set ("report.recurring.sort", "due+,priority-,project+"); // TODO i18n set ("report.recurring.filter", "status:pending parent.any:"); // TODO i18n + + set ("report.waiting.description", "Lists all waiting tasks matching the specified criteria"); // TODO i18n + set ("report.waiting.columns", "id,project,priority,wait,age,description"); // TODO i18n + set ("report.waiting.labels", "ID,Project,Pri,Wait,Age,Description"); // TODO i18n + set ("report.waiting.sort", "wait+,priority-,project+"); // TODO i18n + set ("report.waiting.filter", "status:waiting"); // TODO i18n } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/TDB.cpp b/src/TDB.cpp index ec4772276..59bff5baa 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -168,7 +168,9 @@ int TDB::load (std::vector & tasks, Filter& filter) { ++numberStatusClauses; - if (att->mod () == "" && att->value () == "pending") + if (att->mod () == "" && + (att->value () == "pending" || + att->value () == "waiting")) ++numberSimpleStatusClauses; } } @@ -391,6 +393,7 @@ void TDB::upgrade () int TDB::gc () { int count = 0; + Date now; // Set up a second TDB. Filter filter; @@ -417,6 +420,15 @@ int TDB::gc () completed.push_back (*task); ++count; } + else if (s == Task::waiting) + { + // Wake up tasks that are waiting. + Date wait_date (::atoi (task->get ("wait").c_str ())); + if (now > wait_date) + task->setStatus (Task::pending); + + still_pending.push_back (*task); + } else still_pending.push_back (*task); } diff --git a/src/Task.cpp b/src/Task.cpp index 31e7d5695..1fb9c5098 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -93,6 +93,7 @@ Task::status Task::textToStatus (const std::string& input) else if (input == "completed") return Task::completed; // TODO i18n else if (input == "deleted") return Task::deleted; // TODO i18n else if (input == "recurring") return Task::recurring; // TODO i18n + else if (input == "waiting") return Task::waiting; // TODO i18n return Task::pending; } @@ -104,6 +105,7 @@ std::string Task::statusToText (Task::status s) else if (s == Task::completed) return "completed"; // TODO i18n else if (s == Task::deleted) return "deleted"; // TODO i18n else if (s == Task::recurring) return "recurring"; // TODO i18n + else if (s == Task::waiting) return "waiting"; // TODO i18n return "pending"; } @@ -169,9 +171,9 @@ void Task::legacyParse (const std::string& line) set ("uuid", line.substr (0, 36)); Task::status status = line[37] == '+' ? completed - : line[37] == 'X' ? deleted - : line[37] == 'r' ? recurring - : pending; + : line[37] == 'X' ? deleted + : line[37] == 'r' ? recurring + : pending; set ("status", statusToText (status)); // No i18n @@ -226,9 +228,9 @@ void Task::legacyParse (const std::string& line) set ("uuid", line.substr (0, 36)); Task::status status = line[37] == '+' ? completed - : line[37] == 'X' ? deleted - : line[37] == 'r' ? recurring - : pending; + : line[37] == 'X' ? deleted + : line[37] == 'r' ? recurring + : pending; set ("status", statusToText (status)); // No i18n diff --git a/src/Task.h b/src/Task.h index 350374f09..fbe7d37e9 100644 --- a/src/Task.h +++ b/src/Task.h @@ -45,7 +45,7 @@ public: std::string composeCSV () const; // Status values. - enum status {pending, completed, deleted, recurring /* , retired, deferred */}; + enum status {pending, completed, deleted, recurring, waiting}; // Public data. int id; diff --git a/src/command.cpp b/src/command.cpp index 05cb3fd25..b2181d10f 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -60,6 +60,8 @@ std::string handleAdd () context.task.setStatus (Task::recurring); context.task.set ("mask", ""); } + else if (context.task.has ("wait")) + context.task.setStatus (Task::waiting); else context.task.setStatus (Task::pending); @@ -1216,6 +1218,15 @@ int deltaAttributes (Task& task) att->first != "description" && att->first != "tags") { + // Modifying "wait" changes status. + if (att->first == "wait") + { + if (att->second.value () == "") + task.setStatus (Task::pending); + else + task.setStatus (Task::waiting); + } + if (att->second.value () == "") task.remove (att->first); else diff --git a/src/custom.cpp b/src/custom.cpp index 08b893fbb..b9b378970 100644 --- a/src/custom.cpp +++ b/src/custom.cpp @@ -368,6 +368,26 @@ std::string handleCustomReport (const std::string& report) table.addCell (row, columnCount, "+"); } + else if (*col == "wait") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Wait"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + int row = 0; + std::string wait; + foreach (task, tasks) + { + wait = task->get ("wait"); + if (wait != "") + { + Date dt (::atoi (wait.c_str ())); + wait = dt.toString (context.config.get ("dateformat", "m/d/Y")); + table.addCell (row++, columnCount, wait); + } + } + } + // Common to all columns. // Add underline. if ((context.config.get (std::string ("color"), true) || context.config.get (std::string ("_forcecolor"), false)) && @@ -403,7 +423,8 @@ std::string handleCustomReport (const std::string& report) Table::ascendingPriority : Table::descendingPriority)); - else if (column == "entry" || column == "start" || column == "due") + else if (column == "entry" || column == "start" || column == "due" || + column == "wait") table.sortOn (columnIndex[column], (direction == '+' ? Table::ascendingDate : diff --git a/src/edit.cpp b/src/edit.cpp index b672af0ce..52d4dae63 100644 --- a/src/edit.cpp +++ b/src/edit.cpp @@ -150,6 +150,7 @@ static std::string formatTask (Task task) << " Due: " << formatDate (task, "due") << std::endl << " Until: " << formatDate (task, "until") << std::endl << " Recur: " << task.get ("recur") << std::endl + << " Wait until: " << task.get ("wait") << std::endl << " Parent: " << task.get ("parent") << std::endl << " Foreground color: " << task.get ("fg") << std::endl << " Background color: " << task.get ("bg") << std::endl @@ -403,6 +404,36 @@ static void parseTask (Task& task, const std::string& after) } } + // wait + value = findDate (after, "Wait until:"); + if (value != "") + { + Date edited (::atoi (value.c_str ())); + + if (task.get ("wait") != "") + { + Date original (::atoi (task.get ("wait").c_str ())); + if (!original.sameDay (edited)) + { + std::cout << "Wait date modified." << std::endl; + task.set ("wait", value); + } + } + else + { + std::cout << "Wait date modified." << std::endl; + task.set ("wait", value); + } + } + else + { + if (task.get ("wait") != "") + { + std::cout << "Wait date removed." << std::endl; + task.remove ("wait"); + } + } + // parent value = findValue (after, "Parent:"); if (value != task.get ("parent")) diff --git a/src/import.cpp b/src/import.cpp index 854ba95b5..771062d63 100644 --- a/src/import.cpp +++ b/src/import.cpp @@ -565,6 +565,7 @@ static std::string importTask_1_6_0 (const std::vector & lines) else if (fields[f] == "'recurring'") task.setStatus (Task::recurring); else if (fields[f] == "'deleted'") task.setStatus (Task::deleted); else if (fields[f] == "'completed'") task.setStatus (Task::completed); + else if (fields[f] == "'waiting'") task.setStatus (Task::waiting); break; case 2: // 'tags' @@ -1063,6 +1064,7 @@ static std::string importCSV (const std::vector & lines) if (value == "recurring") task.setStatus (Task::recurring); else if (value == "deleted") task.setStatus (Task::deleted); else if (value == "completed") task.setStatus (Task::completed); + else if (value == "waiting") task.setStatus (Task::waiting); else task.setStatus (Task::pending); } diff --git a/src/recur.cpp b/src/recur.cpp index 2ca793807..e927129a5 100644 --- a/src/recur.cpp +++ b/src/recur.cpp @@ -347,6 +347,7 @@ void updateRecurrenceMask ( mask[index] = (task.getStatus () == Task::pending) ? '-' : (task.getStatus () == Task::completed) ? '+' : (task.getStatus () == Task::deleted) ? 'X' + : (task.getStatus () == Task::waiting) ? 'W' : '?'; it->set ("mask", mask); @@ -361,6 +362,7 @@ void updateRecurrenceMask ( mask += (task.getStatus () == Task::pending) ? '-' : (task.getStatus () == Task::completed) ? '+' : (task.getStatus () == Task::deleted) ? 'X' + : (task.getStatus () == Task::waiting) ? 'W' : '?'; } diff --git a/src/report.cpp b/src/report.cpp index 72a3afe73..124caf30e 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -242,6 +242,7 @@ std::string longUsage () << " fg: Foreground color" << "\n" << " bg: Background color" << "\n" << " limit: Desired number of rows in report" << "\n" + << " wait: Date until task becomes pending" << "\n" << "\n" << "Attribute modifiers improve filters. Supported modifiers are:" << "\n" << " before (synonyms under, below)" << "\n" @@ -416,6 +417,15 @@ std::string handleInfo () } } + // wait + if (task->has ("wait")) + { + row = table.addRow (); + table.addCell (row, 0, "Waiting until"); + Date dt (::atoi (task->get ("wait").c_str ())); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat", "m/d/Y"))); + } + // start if (task->has ("start")) { @@ -539,7 +549,8 @@ std::string handleReportSummary () std::string project = task->get ("project"); ++counter[project]; - if (task->getStatus () == Task::pending) + if (task->getStatus () == Task::pending || + task->getStatus () == Task::waiting) { ++countPending[project]; @@ -1646,6 +1657,7 @@ std::string handleReportStats () int deletedT = 0; int pendingT = 0; int completedT = 0; + int waitingT = 0; int taggedT = 0; int annotationsT = 0; int recurringT = 0; @@ -1662,6 +1674,7 @@ std::string handleReportStats () if (it->getStatus () == Task::pending) ++pendingT; if (it->getStatus () == Task::completed) ++completedT; if (it->getStatus () == Task::recurring) ++recurringT; + if (it->getStatus () == Task::waiting) ++waitingT; time_t entry = ::atoi (it->get ("entry").c_str ()); if (entry < earliest) earliest = entry; @@ -1721,6 +1734,10 @@ std::string handleReportStats () table.addCell (row, 0, "Pending"); table.addCell (row, 1, pendingT); + row = table.addRow (); + table.addCell (row, 0, "Waiting"); + table.addCell (row, 1, waitingT); + row = table.addRow (); table.addCell (row, 0, "Recurring"); table.addCell (row, 1, recurringT); diff --git a/src/valid.cpp b/src/valid.cpp index 3a5294b7b..7083c30e3 100644 --- a/src/valid.cpp +++ b/src/valid.cpp @@ -76,7 +76,8 @@ void validReportColumns (const std::vector & columns) *it != "recurrence_indicator" && *it != "tag_indicator" && *it != "description_only" && - *it != "description") + *it != "description" && + *it != "wait") bad.push_back (*it); if (bad.size ())