From 73ff6ea973947fab60d482426adf7c8c0e98ce84 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 9 Sep 2010 20:49:17 -0400 Subject: [PATCH] Task 2.0.0 Code Salvage --- src/Context.cpp | 4 +- src/File.cpp | 12 +- src/File.h | 3 +- src/main.cpp | 16 ++ src/ui/Clock.cpp | 138 +++++++++++++++ src/ui/Clock.h | 49 ++++++ src/ui/Element.cpp | 152 ++++++++++++++++ src/ui/Element.h | 64 +++++++ src/ui/Keys.cpp | 85 +++++++++ src/ui/Keys.h | 48 +++++ src/ui/Layout.cpp | 172 ++++++++++++++++++ src/ui/Layout.h | 57 ++++++ src/ui/Makefile.am | 8 + src/ui/Message.cpp | 73 ++++++++ src/ui/Message.h | 48 +++++ src/ui/Report.cpp | 80 +++++++++ src/ui/Report.h | 50 ++++++ src/ui/ReportStats.cpp | 385 +++++++++++++++++++++++++++++++++++++++++ src/ui/ReportStats.h | 66 +++++++ src/ui/Stats.cpp | 88 ++++++++++ src/ui/Stats.h | 48 +++++ src/ui/Title.cpp | 69 ++++++++ src/ui/Title.h | 48 +++++ src/ui/UI.cpp | 221 +++++++++++++++++++++++ src/ui/UI.h | 57 ++++++ src/ui/interactive.cpp | 133 ++++++++++++++ src/ui/log.cpp | 217 +++++++++++++++++++++++ src/ui/log.h | 42 +++++ src/ui/resize.cpp | 284 ++++++++++++++++++++++++++++++ 29 files changed, 2713 insertions(+), 4 deletions(-) create mode 100644 src/ui/Clock.cpp create mode 100644 src/ui/Clock.h create mode 100644 src/ui/Element.cpp create mode 100644 src/ui/Element.h create mode 100644 src/ui/Keys.cpp create mode 100644 src/ui/Keys.h create mode 100644 src/ui/Layout.cpp create mode 100644 src/ui/Layout.h create mode 100644 src/ui/Makefile.am create mode 100644 src/ui/Message.cpp create mode 100644 src/ui/Message.h create mode 100644 src/ui/Report.cpp create mode 100644 src/ui/Report.h create mode 100644 src/ui/ReportStats.cpp create mode 100644 src/ui/ReportStats.h create mode 100644 src/ui/Stats.cpp create mode 100644 src/ui/Stats.h create mode 100644 src/ui/Title.cpp create mode 100644 src/ui/Title.h create mode 100644 src/ui/UI.cpp create mode 100644 src/ui/UI.h create mode 100644 src/ui/interactive.cpp create mode 100644 src/ui/log.cpp create mode 100644 src/ui/log.h create mode 100644 src/ui/resize.cpp diff --git a/src/Context.cpp b/src/Context.cpp index 9783948b0..63abd50fa 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -242,8 +242,8 @@ int Context::dispatch (std::string &out) #endif else if (cmd.command == "undo") { handleUndo ( ); } else if (cmd.command == "merge") { tdb.gc (); - handleMerge (out); } - else if (cmd.command == "push") { handlePush (out); } + handleMerge (out); } + else if (cmd.command == "push") { handlePush (out); } else if (cmd.command == "_projects") { rc = handleCompletionProjects (out); } else if (cmd.command == "_tags") { rc = handleCompletionTags (out); } else if (cmd.command == "_commands") { rc = handleCompletionCommands (out); } diff --git a/src/File.cpp b/src/File.cpp index b65eb7bbb..5c546e153 100644 --- a/src/File.cpp +++ b/src/File.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // taskwarrior - a command line task list manager. // -// Copyright 2006 - 2009, Paul Beckingham. +// Copyright 2006 - 2010, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -125,6 +125,16 @@ size_t File::size () const return 0; } +//////////////////////////////////////////////////////////////////////////////// +time_t File::mtime () const +{ + struct stat s; + if (!stat (data.c_str (), &s)) + return s.st_mtime; + + return 0; +} + //////////////////////////////////////////////////////////////////////////////// bool File::create (const std::string& name) { diff --git a/src/File.h b/src/File.h index b6fa67cee..eb3dfa8a8 100644 --- a/src/File.h +++ b/src/File.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // taskwarrior - a command line task list manager. // -// Copyright 2006 - 2009, Paul Beckingham. +// Copyright 2006 - 2010, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -62,6 +62,7 @@ public: virtual mode_t mode (); virtual size_t size () const; + virtual time_t mtime () const; static bool create (const std::string&); static std::string read (const std::string&); diff --git a/src/main.cpp b/src/main.cpp index 6ee83225f..9d9145099 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,6 +50,22 @@ int main (int argc, char** argv) { context.initialize (argc, argv); +/* From 2.0.0 + std::string::size_type task = context.program.find ("/task"); + std::string::size_type t = context.program.find ("/t"); + std::string::size_type cal = context.program.find ("/cal"); + + if (context.program != "task" && + context.program != "t" && + context.program != "cal" && + (task == std::string::npos || context.program.length () != task + 5) && + (t == std::string::npos || context.program.length () != t + 2) && + (cal == std::string::npos || context.program.length () != cal + 4)) + status = context.handleInteractive (); + else + status = context.run (); +*/ + std::string::size_type itask = context.program.find ("/itask"); if (context.program == "itask" || (itask != std::string::npos && context.program.length () == itask + 5)) diff --git a/src/ui/Clock.cpp b/src/ui/Clock.cpp new file mode 100644 index 000000000..f97741d27 --- /dev/null +++ b/src/ui/Clock.cpp @@ -0,0 +1,138 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "log.h" +#include "Clock.h" +#include "Context.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +extern Context context; + +static int refreshDelay; // Override with ui.clock.refresh + +//////////////////////////////////////////////////////////////////////////////// +// This thread sleeps and updates the time every second. +static void tick (int* arg) +{ + Clock* c = (Clock*) arg; + while (c) + { + sleep (refreshDelay); + + // If the element is deinitialized the window goes away, but the thread does + // not. Protect against an attempted redraw of a dead window. + if (c->window != NULL) + c->redraw (); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Create thread to track time. +Clock::Clock () +{ + refreshDelay = context.config.getInteger ("ui.clock.refresh"); + logWrite ("Clock::Clock refreshDelay=%d", refreshDelay); + pthread_create (&ticker, NULL, (void*(*)(void*)) tick, (void*) this); +} + +//////////////////////////////////////////////////////////////////////////////// +Clock::~Clock () +{ + pthread_kill (ticker, SIGQUIT); +} + +//////////////////////////////////////////////////////////////////////////////// +void Clock::redraw () +{ + logWrite ("Clock::redraw"); + + time_t now; + time (&now); + struct tm* t = localtime (&now); + + // TODO Load these from config. + const char* formats[] = + { + // These should remain sorted from longest + // to shortest, in their expanded form, + // shown below. + " %A %e %B %Y, wk%U, %H:%M:%S %z ", // " Sunday 18 October 2009, wk42, 22:42:39 -0400 " + " %A %e %B %Y, wk%U, %H:%M:%S %Z ", // " Sunday 18 October 2009, wk42, 22:42:39 EDT " + " %A %e %B %Y wk%U, %H:%M:%S %Z ", // " Sunday 18 October 2009 wk42, 22:42:39 EDT " + " %A %e %B %Y wk%U %H:%M:%S %Z ", // " Sunday 18 October 2009 wk42 22:42:39 EDT " + " %A %e %B %Y, wk%U, %H:%M:%S ", // " Sunday 18 October 2009, wk42, 22:42:39 " + " %A %e %B %Y, wk%U, %H:%M:%S ", // " Sunday 18 October 2009, wk42, 22:42:39 " + " %A %e %B %Y wk%U, %H:%M:%S ", // " Sunday 18 October 2009 wk42, 22:42:39 " + " %A %e %B %Y wk%U %H:%M:%S ", // " Sunday 18 October 2009 wk42 22:42:39 " + " %A %e %b %Y,wk%U, %H:%M:%S ", // " Sunday 18 Oct 2009, wk42, 22:42:39 " + " %A %e %b %Y wk%U, %H:%M:%S ", // " Sunday 18 Oct 2009 wk42, 22:42:39 " + " %A %e %b %Y wk%U %H:%M:%S ", // " Sunday 18 Oct 2009 wk42 22:42:39 " + " %a %e %b %Y, wk%U, %H:%M:%S ", // " Sun 18 Oct 2009, wk42, 22:42:39 " + " %a %e %b %Y wk%U, %H:%M:%S ", // " Sun 18 Oct 2009 wk42, 22:42:39 " + " %a %e %b %Y, %H:%M:%S ", // " Sun 18 Oct 2009, 22:42:39 " + " %a %e %b %Y %H:%M:%S ", // " Sun 18 Oct 2009 22:42:39 " + " %a %e %b, %H:%M:%S ", // " Sun 18 Oct, 22:42:39 " + " %a %e %b %H:%M:%S ", // " Sun 18 Oct 22:42:39 " + " %e %b, %H:%M:%S ", // " 18 Oct, 22:42:39 " + " %e %b %H:%M:%S ", // " 18 Oct 22:42:39 " + " %H:%M:%S ", // " 22:42:39 " + "%H:%M:%S", // "22:42:39" + " %H:%M ", // " 22:42 " + "%H:%M", // "22:42" + "", + }; + + // TODO %U (Sun), $W (Mon) + // TODO i18n "wk" + + // Render them all, but only keep the longest one that fits. + std::string keeper = ""; + + char buffer[128]; + for (int i = 0; formats[i][0]; ++i) + { + strftime (buffer, 128, formats[i], t); + size_t len = strlen (buffer); + if (len <= (size_t) width && len > keeper.length ()) + keeper = buffer; + } + + pthread_mutex_lock (&conch); + wbkgd (window, COLOR_PAIR (5)); + wbkgdset (window, COLOR_PAIR(5) | A_DIM); + mvwaddstr (window, 0, width - keeper.length (), keeper.c_str ()); + wrefresh (window); + pthread_mutex_unlock (&conch); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/Clock.h b/src/ui/Clock.h new file mode 100644 index 000000000..8d920c34b --- /dev/null +++ b/src/ui/Clock.h @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_CLOCK +#define INCLUDED_CLOCK + +#include +#include "Element.h" + +class Clock : public Element +{ +public: + Clock (); + Clock (const Clock&); + Clock& operator= (const Clock&); + ~Clock (); + + void redraw (); + +private: + pthread_t ticker; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/Element.cpp b/src/ui/Element.cpp new file mode 100644 index 000000000..bd984db23 --- /dev/null +++ b/src/ui/Element.cpp @@ -0,0 +1,152 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "log.h" +#include "Element.h" +#include "Report.h" +#include "ReportStats.h" +#include "Title.h" +#include "Clock.h" +#include "Keys.h" +#include "Message.h" +#include "Stats.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +//////////////////////////////////////////////////////////////////////////////// +// Factory for Element construction, based on spec. +Element* Element::factory (const std::string& name) +{ +// logWrite ("Element::factory %s", name.c_str ()); + + Element* e; + if (name == "report") e = new Report (); // TODO Remove. + else if (name == "report.stats") e = new ReportStats (); + else if (name == "title") e = new Title (); + else if (name == "clock") e = new Clock (); + else if (name == "keys") e = new Keys (); + else if (name == "message") e = new Message (); + else if (name == "stats") e = new Stats (); + else + throw std::string ("Unrecognized element type: '") + name + "'"; + + e->type = name; + return e; +} + +//////////////////////////////////////////////////////////////////////////////// +Element::Element () +: left (0) +, top (0) +, width (0) +, height (0) +, type ("") +, window (NULL) +{ +// logWrite ("Element::Element"); +} + +//////////////////////////////////////////////////////////////////////////////// +Element::~Element () +{ + deinitialize (); +} + +//////////////////////////////////////////////////////////////////////////////// +// When a layout is selected, all the elements are initialized, which means the +// window is created. +void Element::initialize () +{ +// logWrite ("Element::initialize %s", type.c_str ()); + + window = newwin (height, width, top, left); +} + +//////////////////////////////////////////////////////////////////////////////// +// When a layout is switched out, the elements are deinitialized, which means +// the window is deleted. The original specs are preserved, and ready for a +// possible later initialize if the layout is switched in. +void Element::deinitialize () +{ +// logWrite ("Element::deinitialize %s", type.c_str ()); + + if (window) + { + pthread_mutex_lock (&conch); + delwin (window); + pthread_mutex_unlock (&conch); + + window = NULL; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Notification of new size for stdscr, triggering a size recalc. +void Element::recalc (int l, int t, int w, int h) +{ + left = l; + top = t; + width = w; + height = h; + logWrite ("Element::recalc %-7s [l%d,t%d,w%d,h%d]", type.c_str (), left, top, width, height); +} + +//////////////////////////////////////////////////////////////////////////////// +void Element::relocate () +{ +// logWrite ("Element::relocate %s", type.c_str ()); + + if (window) + { + pthread_mutex_lock (&conch); + mvwin (window, top, left); + wresize (window, height, width); + pthread_mutex_unlock (&conch); + + logWrite ("Element::relocate %-7s [l%d,t%d,w%d,h%d]", type.c_str (), left, top, height, width); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Default event handler for Element. +bool Element::event (int e) +{ +// logWrite ("Element::event %s %d", type.c_str (), e); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Default repaint handler for Element. +void Element::redraw () +{ + logWrite ("Element::redraw %s", type.c_str ()); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/Element.h b/src/ui/Element.h new file mode 100644 index 000000000..f7244f663 --- /dev/null +++ b/src/ui/Element.h @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_ELEMENT +#define INCLUDED_ELEMENT + +#include +#include +class Layout; + +class Element +{ +public: + static Element* factory (const std::string&); + + Element (); + Element (const Element&); // Unimplemented + Element& operator= (const Element&); // Unimplemented + virtual ~Element (); + + virtual void initialize (); + virtual void deinitialize (); + + virtual void recalc (int, int, int, int); + virtual void relocate (); + virtual bool event (int); + virtual void redraw (); + +public: + // The actual dimensions of the window. These change when stdscr resizes. + int left; + int top; + int width; + int height; + std::string type; + WINDOW* window; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/Keys.cpp b/src/ui/Keys.cpp new file mode 100644 index 000000000..3b88113f8 --- /dev/null +++ b/src/ui/Keys.cpp @@ -0,0 +1,85 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include "log.h" +#include "Keys.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +//////////////////////////////////////////////////////////////////////////////// +Keys::Keys () +{ +// logWrite ("Keys::Keys"); +} + +//////////////////////////////////////////////////////////////////////////////// +Keys::~Keys () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void Keys::redraw () +{ + logWrite ("Keys::redraw"); + + pthread_mutex_lock (&conch); + wbkgd (window, COLOR_PAIR (1)); + + if (width >= 5) + { + wbkgdset (window, COLOR_PAIR(1) | A_BOLD); + mvwaddstr (window, 0, 0, " q"); + + wbkgdset (window, COLOR_PAIR(1)); + mvwaddstr (window, 0, 2, "uit"); + + if (width >= 13) + { + wbkgdset (window, COLOR_PAIR(1) | A_BOLD); + mvwaddstr (window, 0, 5, " d"); + + wbkgdset (window, COLOR_PAIR(1)); + mvwaddstr (window, 0, 7, "efault"); + + if (width >= 20) + { + wbkgdset (window, COLOR_PAIR(1) | A_BOLD); + mvwaddstr (window, 0, 13, " s"); + + wbkgdset (window, COLOR_PAIR(1)); + mvwaddstr (window, 0, 15, "tats"); + } + } + } + + wrefresh (window); + pthread_mutex_unlock (&conch); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/Keys.h b/src/ui/Keys.h new file mode 100644 index 000000000..8b2ec2c7f --- /dev/null +++ b/src/ui/Keys.h @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_KEYS +#define INCLUDED_KEYS + +#include +#include "Element.h" + +class Keys : public Element +{ +public: + Keys (); + Keys (const Keys&); + Keys& operator= (const Keys&); + ~Keys (); + + void redraw (); + +private: +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/Layout.cpp b/src/ui/Layout.cpp new file mode 100644 index 000000000..f6152c50c --- /dev/null +++ b/src/ui/Layout.cpp @@ -0,0 +1,172 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "log.h" +#include "Rectangle.h" +#include "Layout.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +//////////////////////////////////////////////////////////////////////////////// +Layout::Layout (const std::string& spec) +: definition (spec) +{ + logWrite ("Layout::Layout %s", spec.c_str ()); + + // Invoke the resize just to extract the element names. + void resize (const std::string&, const int, const int, std::string& name, std::map &); + std::map defs; + resize (definition, 80, 24, name, defs); + logWrite ("Layout::Layout name=%s", name.c_str ()); + + // Create an element for each panel in the definition. + std::map ::iterator it; + for (it = defs.begin (); it != defs.end (); ++it) + { + Element* e = Element::factory (it->first); + elements[e->type] = e; + } +} + +//////////////////////////////////////////////////////////////////////////////// +Layout::~Layout () +{ + std::map ::iterator it; + for (it = elements.begin (); it != elements.end (); ++it) + delete it->second; + + elements.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// +void Layout::initialize () +{ + logWrite ("Layout::initialize"); + + // Iterate over elements[] (specs) and create windows. + std::map ::iterator it; + for (it = elements.begin (); it != elements.end (); ++it) + it->second->initialize (); +} + +//////////////////////////////////////////////////////////////////////////////// +void Layout::deinitialize () +{ + logWrite ("Layout::deinitialize"); + + std::map ::iterator it; + for (it = elements.begin (); it != elements.end (); ++it) + it->second->deinitialize (); + + // At this point the display should be stale but not cleared. +} + +//////////////////////////////////////////////////////////////////////////////// +bool Layout::event (int e) +{ + switch (e) + { + case 12: + logWrite ("Layout::event Ctrl-L"); + this->redraw (true); + return true; // handled. + break; + + default: + logWrite ("Layout::event %d delegated", e); + { + std::map ::iterator it; + for (it = elements.begin (); it != elements.end (); ++it) + it->second->event (e); + } + return true; // handled. + break; + } + + return false; // not handled. +} + +//////////////////////////////////////////////////////////////////////////////// +void Layout::recalc (int w, int h) +{ + logWrite ("Layout::recalc %d,%d", w, h); + + // Apply layout definition to [w,h], yielding rectangles for each element. + void resize (const std::string&, const int, const int, std::string& name, std::map &); + std::string name; + std::map defs; + resize (definition, w, h, name, defs); + + // Relocate each element. + std::map ::iterator it; + for (it = defs.begin (); it != defs.end (); ++it) + { + Element* e = elements[it->first]; + if (e) + { + e->recalc ( + it->second.left, + it->second.top, + it->second.width, + it->second.height); + e->relocate (); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Redrawing the layout consists of touching and refreshing all the elements. +void Layout::redraw (bool force /* = false */) +{ + logWrite ("Layout::redraw"); + + // This is the magic line that makes resize work. I wish I knew why. + refresh (); + + std::map ::iterator it; + for (it = elements.begin (); it != elements.end (); ++it) + { + it->second->redraw (); + if (force) + { + pthread_mutex_lock (&conch); + touchwin (it->second->window); + pthread_mutex_unlock (&conch); + } + + pthread_mutex_lock (&conch); + wrefresh (it->second->window); + pthread_mutex_unlock (&conch); + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/Layout.h b/src/ui/Layout.h new file mode 100644 index 000000000..1ab74da9a --- /dev/null +++ b/src/ui/Layout.h @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_LAYOUT +#define INCLUDED_LAYOUT + +#include +#include +#include +#include "Element.h" + +class Layout +{ +public: + Layout (const std::string&); + Layout (const Layout&); + Layout& operator= (const Layout&); + ~Layout (); + + void initialize (); + void deinitialize (); + bool event (int); + void recalc (int, int); + void redraw (bool force = false); + +private: + std::string definition; + std::string name; + std::map elements; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/Makefile.am b/src/ui/Makefile.am new file mode 100644 index 000000000..201c16885 --- /dev/null +++ b/src/ui/Makefile.am @@ -0,0 +1,8 @@ +libtaskui_SHORTNAME = ui +noinst_LIBRARIES = libtaskui.a +libtaskui_a_SOURCES = UI.cpp Layout.cpp Element.cpp Clock.cpp Keys.cpp \ + Message.cpp Report.cpp Stats.cpp Title.cpp resize.cpp \ + ReportStats.cpp log.cpp \ + Clock.h Element.h Keys.h Layout.h Message.h Report.h \ + ReportStats.h Stats.h Title.h UI.h log.h +libtaskui_a_CPPFLAGS = -I../util -I.. diff --git a/src/ui/Message.cpp b/src/ui/Message.cpp new file mode 100644 index 000000000..7f362add2 --- /dev/null +++ b/src/ui/Message.cpp @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include "log.h" +#include "Message.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +//////////////////////////////////////////////////////////////////////////////// +Message::Message () +{ +// logWrite ("Message::Message"); +} + +//////////////////////////////////////////////////////////////////////////////// +Message::~Message () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void Message::redraw () +{ + logWrite ("Message::redraw"); + + // Truncate the message, if necessary. + std::string s = "Using database ~/.task"; + if (width - 1 < (int) s.length ()) + s = s.substr (0, width - 4) + "..."; + + // Need at least space for blank + 1 char + ellipsis. + if (width <= 5) + s = ""; + + pthread_mutex_lock (&conch); + wbkgd (window, COLOR_PAIR (5)); + wbkgdset (window, COLOR_PAIR(5) | A_DIM); + + if (width <= 5) + touchwin (window); + + mvwaddstr (window, 0, 1, s.c_str ()); + + wrefresh (window); + pthread_mutex_unlock (&conch); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/Message.h b/src/ui/Message.h new file mode 100644 index 000000000..a54b59215 --- /dev/null +++ b/src/ui/Message.h @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_MESSAGE +#define INCLUDED_MESSAGE + +#include +#include "Element.h" + +class Message : public Element +{ +public: + Message (); + Message (const Message&); + Message& operator= (const Message&); + ~Message (); + + void redraw (); + +private: +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/Report.cpp b/src/ui/Report.cpp new file mode 100644 index 000000000..17773d7ab --- /dev/null +++ b/src/ui/Report.cpp @@ -0,0 +1,80 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include "log.h" +#include "Report.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +//////////////////////////////////////////////////////////////////////////////// +Report::Report () +{ +// logWrite ("Report::Report"); +} + +//////////////////////////////////////////////////////////////////////////////// +Report::~Report () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +bool Report::event (int e) +{ + switch (e) + { + case KEY_MOUSE: + { + MEVENT m; + getmouse (&m); + logWrite ("Report::event KEY_MOUSE [%d,%d] %x (%s)", + m.x, + m.y, + m.bstate, + ((m.x >= left && m.x < left + width && m.y >= top && m.y < top + height) ? "hit" : "miss")); + } + return true; + break; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +void Report::redraw () +{ + logWrite ("Report::redraw"); + + pthread_mutex_lock (&conch); + wbkgd (window, COLOR_PAIR (20)); + mvwaddstr (window, 0, 0, "default report"); + wrefresh (window); + pthread_mutex_unlock (&conch); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/Report.h b/src/ui/Report.h new file mode 100644 index 000000000..5f845e217 --- /dev/null +++ b/src/ui/Report.h @@ -0,0 +1,50 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_REPORT +#define INCLUDED_REPORT + +#include +#include "Element.h" + +class Report : public Element +{ +public: + Report (); + Report (const Report&); + Report& operator= (const Report&); + ~Report (); + + bool event (int); + void redraw (); + +private: + bool value; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/ReportStats.cpp b/src/ui/ReportStats.cpp new file mode 100644 index 000000000..3a30efdfb --- /dev/null +++ b/src/ui/ReportStats.cpp @@ -0,0 +1,385 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "log.h" +#include "ReportStats.h" +#include "Context.h" +#include "File.h" +#include "Path.h" +#include "util.h" +#include "text.h" +#include "main.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +extern Context context; + +static int refreshDelay; // Override with ui.stats.refresh + +//////////////////////////////////////////////////////////////////////////////// +// This thread sleeps and updates the time every second. +static void tick (int* arg) +{ + ReportStats* r = (ReportStats *) arg; + do + { + // If the element is deinitialized the window goes away, but the thread does + // not. Protect against an attempted redraw of a dead window. + if (r->window != NULL && + r->dataChanged ()) + { + r->gatherStats (); + r->redraw (); + } + + sleep (refreshDelay); + } + while (arg); +} + +//////////////////////////////////////////////////////////////////////////////// +ReportStats::ReportStats () +: highlight_shown (false) +{ +// logWrite ("ReportStats::ReportStats"); + refreshDelay = context.config.getInteger ("ui.stats.refresh"); + logWrite ("ReportStats::ReportStats refreshDelay=%d", refreshDelay); + pthread_create (&ticker, NULL, (void*(*)(void*)) tick, (void*) this); +} + +//////////////////////////////////////////////////////////////////////////////// +ReportStats::~ReportStats () +{ + pthread_kill (ticker, SIGQUIT); +} + +//////////////////////////////////////////////////////////////////////////////// +void ReportStats::initialize () +{ + Element::initialize (); + scrollok (window, TRUE); +// idlok (window, TRUE); +} + +//////////////////////////////////////////////////////////////////////////////// +// TODO Replace with Sensor object. +bool ReportStats::dataChanged () +{ + bool status = false; + + struct stat s; + std::string location = Path::expand (context.config.get ("data.location")); + std::string file = location + "/pending.data"; + stat (file.c_str (), &s); + if (s.st_mtime != stat_pending.st_mtime) + { + stat_pending = s; + status = true; + } + + file = location + "/completed.data"; + stat (file.c_str (), &s); + if (s.st_mtime != stat_completed.st_mtime) + { + stat_completed = s; + status = true; + } + + file = location + "/undo.data"; + stat (file.c_str (), &s); + if (s.st_mtime != stat_undo.st_mtime) + { + stat_undo = s; + status = true; + } + + if (highlight_shown) + status = true; + + return status; +} + +//////////////////////////////////////////////////////////////////////////////// +// Maintains two sets of data, previous and current. All newly gathered data +// goes into current. Next iteration, the current is moved to previous. +// +// This two-stage tracking allows detection of changes from one refresh to the +// next, and therefore the highlighting. +void ReportStats::gatherStats () +{ + // The new becomes the old. + previous = current; + + // Data size + size_t dataSize = stat_pending.st_size + + stat_completed.st_size + + stat_undo.st_size; + + // Undo transactions + std::string location = Path::expand (context.config.get ("data.location")); + std::string file = location + "/undo.data"; + + std::vector undo; + File::read (file, undo); + int undoCount = 0; + foreach (tx, undo) + if (tx->substr (0, 3) == "---") + ++undoCount; + + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + Date now; + time_t earliest = time (NULL); + time_t latest = 1; + int totalT = 0; + int deletedT = 0; + int pendingT = 0; + int completedT = 0; + int waitingT = 0; + int taggedT = 0; + int annotationsT = 0; + int recurringT = 0; + float daysPending = 0.0; + int descLength = 0; + std::map allTags; + std::map allProjects; + + std::vector ::iterator it; + for (it = tasks.begin (); it != tasks.end (); ++it) + { + ++totalT; + if (it->getStatus () == Task::deleted) ++deletedT; + 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; + if (entry > latest) latest = entry; + + if (it->getStatus () == Task::completed) + { + time_t end = atoi (it->get ("end").c_str ()); + daysPending += (end - entry) / 86400.0; + } + + if (it->getStatus () == Task::pending) + daysPending += (now - entry) / 86400.0; + + descLength += it->get ("description").length (); + + std::vector annotations; + it->getAnnotations (annotations); + annotationsT += annotations.size (); + + std::vector tags; + it->getTags (tags); + if (tags.size ()) ++taggedT; + + foreach (t, tags) + allTags[*t] = 0; + + std::string project = it->get ("project"); + if (project != "") + allProjects[project] = 0; + } + + current["pending"] = commify (pendingT); + current["waiting"] = commify (waitingT); + current["recurring"] = commify (recurringT); + current["completed"] = commify (completedT); + current["deleted"] = commify (deletedT); + current["total"] = commify (totalT); + current["annotations"] = commify (annotationsT); + current["tags"] = commify ((int)allTags.size ()); + current["projects"] = commify ((int)allProjects.size ()); + current["size"] = formatBytes (dataSize); + current["undo"] = commify (undoCount); + + std::stringstream s; + if (totalT) + s << std::setprecision (3) << (100.0 * taggedT / totalT); + else + s << 0; + + s << "%"; + current["tagged"] = s.str (); + + if (tasks.size ()) + { + Date e (earliest); + Date l (latest); + + current["oldest"] = e.toString (context.config.get ("dateformat")); + current["newest"] = l.toString (context.config.get ("dateformat")); + current["since"] = formatSeconds (latest - earliest); + } + else + { + current["oldest"] = "-"; + current["newest"] = "-"; + current["since"] = "-"; + } + + if (totalT) + current["add_every"] = formatSeconds ((latest - earliest) / totalT); + else + current["add_every"] = "-"; + + if (completedT) + current["complete_every"] = formatSeconds ((latest - earliest) / completedT); + else + current["complete_every"] = "-"; + + if (deletedT) + current["delete_every"] = formatSeconds ((latest - earliest) / deletedT); + else + current["delete_every"] = "-"; + + if (pendingT || completedT) + current["time"] = formatSeconds ((int) ((daysPending / (pendingT + completedT)) * 86400)); + else + current["time"] = "-"; + + if (totalT) + { + std::stringstream value; + value << (int) (descLength / totalT) << " characters"; + current["desc"] = value.str (); + } + else + current["desc"] = "-"; +} + +//////////////////////////////////////////////////////////////////////////////// +bool ReportStats::event (int e) +{ + switch (e) + { + case KEY_UP: + logWrite ("ReportStats::event KEY_UP"); + wscrl (window, -1); + wrefresh (window); + break; + + case KEY_DOWN: + logWrite ("ReportStats::event KEY_DOWN"); + wscrl (window, 1); + wrefresh (window); + break; + + case KEY_MOUSE: + { + MEVENT m; + getmouse (&m); + logWrite ("ReportStats::event KEY_MOUSE [%d,%d] %x (%s)", + m.x, + m.y, + m.bstate, + ((m.x >= left && m.x < left + width && m.y >= top && m.y < top + height) ? "hit" : "miss")); + } + return true; + break; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +void ReportStats::redraw () +{ + logWrite ("ReportStats::redraw"); + + pthread_mutex_lock (&conch); + wbkgd (window, COLOR_PAIR (20) | A_DIM); + wbkgdset (window, COLOR_PAIR(20) | A_DIM); + + std::stringstream title; + title << "Live Statistics (updates every " + << refreshDelay + << " seconds)"; + mvwaddstr (window, 0, 0, title.str ().c_str ()); + + highlight_shown = false; + renderItem (2, 24, "Pending tasks", "pending"); + renderItem (3, 24, "Waiting tasks", "waiting"); + renderItem (4, 24, "Recurring tasks", "recurring"); + renderItem (5, 24, "Completed tasks", "completed"); + renderItem (6, 24, "Deleted tasks", "deleted"); + renderItem (7, 24, "Total tasks", "total"); + renderItem (8, 24, "Total annotations", "annotations"); + renderItem (9, 24, "Unique tags", "tags"); + renderItem (10, 24, "Projects", "projects"); + renderItem (11, 24, "Data size", "size"); + renderItem (12, 24, "Undo transactions", "undo"); + renderItem (13, 24, "Tasks tagged", "tagged"); + renderItem (14, 24, "Oldest task", "oldest"); + renderItem (15, 24, "Newest task", "newest"); + renderItem (16, 24, "Task used for", "since"); + renderItem (17, 24, "Task added every", "add_every"); + renderItem (18, 24, "Task completed every", "complete_every"); + renderItem (19, 24, "Task deleted every", "delete_every"); + renderItem (20, 24, "Average time pending", "time"); + renderItem (21, 24, "Average desc length", "desc"); + + wrefresh (window); + pthread_mutex_unlock (&conch); +} + +//////////////////////////////////////////////////////////////////////////////// +void ReportStats::renderItem ( + int row, + int col, + const std::string& description, + const std::string& key) +{ + wbkgdset (window, COLOR_PAIR(20) | A_DIM); + mvwaddstr (window, row, 0, description.c_str ()); + + if (current[key] != previous[key]) + { + wbkgdset (window, COLOR_PAIR(3) | A_BOLD); + highlight_shown = true; + } + + mvwaddstr (window, row, col, current[key].c_str ()); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/ReportStats.h b/src/ui/ReportStats.h new file mode 100644 index 000000000..ebf516865 --- /dev/null +++ b/src/ui/ReportStats.h @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_REPORTSTATS +#define INCLUDED_REPORTSTATS + +#include +#include +#include +#include +#include +#include "Element.h" + +class ReportStats : public Element +{ +public: + ReportStats (); + ReportStats (const ReportStats&); + ReportStats& operator= (const ReportStats&); + ~ReportStats (); + + void initialize (); + bool dataChanged (); + void gatherStats (); + bool event (int); + void redraw (); + void renderItem (int, int, const std::string&, const std::string&); + +public: + struct stat stat_pending; + struct stat stat_completed; + struct stat stat_undo; + +private: + pthread_t ticker; + std::map previous; + std::map current; + bool highlight_shown; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/Stats.cpp b/src/ui/Stats.cpp new file mode 100644 index 000000000..48f017185 --- /dev/null +++ b/src/ui/Stats.cpp @@ -0,0 +1,88 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include "log.h" +#include "Stats.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +//////////////////////////////////////////////////////////////////////////////// +Stats::Stats () +{ +// logWrite ("Stats::Stats"); +} + +//////////////////////////////////////////////////////////////////////////////// +Stats::~Stats () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void Stats::redraw () +{ + logWrite ("Stats::redraw"); + + pthread_mutex_lock (&conch); + wbkgd (window, COLOR_PAIR(1) | A_DIM); + + // Allowing 5-digits for tasks. + if (height >= 3) + { + if (width >= 19) mvwaddstr (window, 2, 6, "Waiting tasks"); + else if (width >= 13) mvwaddstr (window, 2, 6, "Waiting"); + else if (width >= 10) mvwaddstr (window, 2, 6, "Wait"); + else if (width >= 7) mvwaddstr (window, 2, 6, "W"); + } + + if (height >= 2) + { + if (width >= 21) mvwaddstr (window, 1, 6, "Completed tasks"); + else if (width >= 15) mvwaddstr (window, 1, 6, "Completed"); + else if (width >= 10) mvwaddstr (window, 1, 6, "Comp"); + else if (width >= 7) mvwaddstr (window, 1, 6, "C"); + } + + if (width >= 19) mvwaddstr (window, 0, 6, "Pending tasks"); + else if (width >= 13) mvwaddstr (window, 0, 6, "Pending"); + else if (width >= 10) mvwaddstr (window, 0, 6, "Pend"); + else if (width >= 7) mvwaddstr (window, 0, 6, "P"); + + if (width >= 7) + { + wbkgdset (window, COLOR_PAIR(1) | A_BOLD); + mvwaddstr (window, 0, 0, "_____"); + if (height >= 2) mvwaddstr (window, 1, 0, "_____"); + if (height >= 3) mvwaddstr (window, 2, 0, "_____"); + } + + wrefresh (window); + pthread_mutex_unlock (&conch); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/Stats.h b/src/ui/Stats.h new file mode 100644 index 000000000..9c85dfc08 --- /dev/null +++ b/src/ui/Stats.h @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_STATS +#define INCLUDED_STATS + +#include +#include "Element.h" + +class Stats : public Element +{ +public: + Stats (); + Stats (const Stats&); + Stats& operator= (const Stats&); + ~Stats (); + + void redraw (); + +private: +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/Title.cpp b/src/ui/Title.cpp new file mode 100644 index 000000000..2f50012b8 --- /dev/null +++ b/src/ui/Title.cpp @@ -0,0 +1,69 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include "log.h" +#include "Title.h" + +// Constriction point for ncurses calls. +extern pthread_mutex_t conch; + +//////////////////////////////////////////////////////////////////////////////// +Title::Title () +{ +// logWrite ("Title::Title"); +} + +//////////////////////////////////////////////////////////////////////////////// +Title::~Title () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void Title::redraw () +{ + logWrite ("Title::redraw [%d,%d]", width, height); + pthread_mutex_lock (&conch); + + wbkgd (window, COLOR_PAIR (1) | ' '); + if (width >= 21) mvwaddstr (window, 0, 1, "task 2.0.0 pre-alpha"); + else if (width >= 11) mvwaddstr (window, 0, 1, "task 2.0.0"); + else if (width >= 9) mvwaddstr (window, 0, 1, "task 2.0"); + else if (width >= 5) mvwaddstr (window, 0, 1, "task"); + + if (height >= 2) + { + wbkgdset (window, COLOR_PAIR(8)); + if (width >= 23) mvwaddstr (window, 1, 1, "http://taskwarrior.org"); + else if (width >= 16) mvwaddstr (window, 1, 1, "taskwarrior.org"); + } + + wrefresh (window); + pthread_mutex_unlock (&conch); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/Title.h b/src/ui/Title.h new file mode 100644 index 000000000..de53a6281 --- /dev/null +++ b/src/ui/Title.h @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_TITLE +#define INCLUDED_TITLE + +#include +#include "Element.h" + +class Title : public Element +{ +public: + Title (); + Title (const Title&); + Title& operator= (const Title&); + ~Title (); + + void redraw (); + +private: +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/UI.cpp b/src/ui/UI.cpp new file mode 100644 index 000000000..21807dfe7 --- /dev/null +++ b/src/ui/UI.cpp @@ -0,0 +1,221 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "log.h" +#include "UI.h" + +// Constriction point for ncurses calls. +pthread_mutex_t conch = PTHREAD_MUTEX_INITIALIZER; + +//////////////////////////////////////////////////////////////////////////////// +UI::UI () +: current (NULL) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +UI::~UI () +{ + std::map ::iterator it; + for (it = layouts.begin (); it != layouts.end (); ++it) + delete it->second; + + layouts.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// +void UI::add (const std::string& name, Layout* layout) +{ + logWrite ("UI::add %s", name.c_str ()); + layouts[name] = layout; + + // First layout added automatically becomes the current layout. Subsequent + // layouts accumulate. + if (current == NULL) + current = layout; +} + +//////////////////////////////////////////////////////////////////////////////// +void UI::switchLayout (const std::string& name) +{ + logWrite ("UI::switchLayout %s", name.c_str ()); + + if (layouts.find (name) == layouts.end ()) + throw std::string ("Cannot switch to non-existent layout '") + name + "'"; + + // Only switch if the proposed layout is not the current layout. + if (layouts[name] != current) + { + // Close the old windows. + current->deinitialize (); + + // Set the new current layout. + current = layouts[name]; + + // Create the new windows. + current->initialize (); + + // Need a size recalc, because the new current layout may have been used + // before at a different size. + this->recalc (COLS, LINES); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void UI::initialize () +{ + logWrite ("UI::initialize"); + + // Rainbow. + init_pair (1, COLOR_WHITE, COLOR_BLUE); + init_pair (2, COLOR_WHITE, COLOR_RED); + init_pair (3, COLOR_BLACK, COLOR_GREEN); + init_pair (4, COLOR_WHITE, COLOR_MAGENTA); + init_pair (5, COLOR_BLACK, COLOR_CYAN); + init_pair (6, COLOR_BLACK, COLOR_YELLOW); + init_pair (7, COLOR_BLACK, COLOR_WHITE); + init_pair (8, COLOR_CYAN, COLOR_BLUE); + init_pair (9, COLOR_BLUE, COLOR_CYAN); + init_pair (10, COLOR_YELLOW, COLOR_BLUE); + +// init_pair (11, COLOR_GREEN, COLOR_BLACK); + + // Plain. + init_pair (20, COLOR_WHITE, COLOR_BLACK); + + // Propagate to current layout. + current->initialize (); +} + +//////////////////////////////////////////////////////////////////////////////// +void UI::deinitialize () +{ + logWrite ("UI::deinitialize"); + + current->deinitialize (); +} + +//////////////////////////////////////////////////////////////////////////////// +void UI::interactive () +{ + logWrite ("UI::interactive"); + + if (!current) + throw std::string ("Cannot start interactive mode without an initial layout."); + + initscr (); + logWrite ("UI::interactive ncurses started"); + refresh (); // Blank screen. + curs_set (0); + + if (has_colors ()) + start_color (); + + this->recalc (COLS, LINES); + this->initialize (); + current->redraw (); + + keypad (stdscr, TRUE); + noecho (); + nl (); + raw (); + cbreak (); + + mousemask (ALL_MOUSE_EVENTS, NULL); + + while (this->event (getch ())) + ; + + this->deinitialize (); + endwin (); + logWrite ("UI::interactive ncurses stopped"); +} + +//////////////////////////////////////////////////////////////////////////////// +bool UI::event (int e) +{ + switch (e) + { + case 'd': // Default layout + switchLayout ("default"); + this->recalc (COLS, LINES); + current->redraw (true); + break; + + case 's': // report.stats layout + switchLayout ("report.stats"); + this->recalc (COLS, LINES); + current->redraw (true); + break; + + case 'Q': // Quit. + case 'q': // quit. + logWrite ("UI::event %c", (char)e); + return false; + break; + + case ERR: // No need to propagate this. + logWrite ("UI::event ERR ignored"); + break; + + case KEY_RESIZE: // This gets propagated through UI::recalc. + logWrite ("UI::event KEY_RESIZE"); + this->recalc (COLS, LINES); + current->redraw (true); // TODO has no apparent effect. + break; + + default: // Unhandled events are propagated. + // Ctrl-L is handled by the layout. + logWrite ("UI::event %d delegated", e); + current->event (e); + break; + } + + return true; // Continue; +} + +//////////////////////////////////////////////////////////////////////////////// +// This is called at the beginning, to calculate element sizes, and on every +// window resize. +void UI::recalc (int w, int h) +{ + logWrite ("UI::recalc %d,%d", w, h); + + // The current layout needs to know. + current->recalc (w, h); + + // Park the cursor. + // TODO Evaluate whether this is needed. + pthread_mutex_lock (&conch); + move (h - 1, w - 1); + pthread_mutex_unlock (&conch); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/UI.h b/src/ui/UI.h new file mode 100644 index 000000000..9d79e2ee2 --- /dev/null +++ b/src/ui/UI.h @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_UI +#define INCLUDED_UI + +#include +#include +#include "Layout.h" + +class UI +{ +public: + UI (); + UI (const UI&); + UI& operator= (const UI&); + ~UI (); + + void add (const std::string&, Layout*); + void switchLayout (const std::string&); + void initialize (); + void deinitialize (); + void interactive (); + void recalc (int, int); + bool event (int); + +private: + std::map layouts; + Layout* current; // Points to one of the layouts. +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/ui/interactive.cpp b/src/ui/interactive.cpp new file mode 100644 index 000000000..847eece20 --- /dev/null +++ b/src/ui/interactive.cpp @@ -0,0 +1,133 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "Context.h" +#include "main.h" +#include "log.h" +#include "Layout.h" +#include "i18n.h" +#include "../auto.h" + +#ifdef HAVE_LIBNCURSES +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// +int Context::handleInteractive () +{ +#ifdef HAVE_LIBNCURSES + // TODO Need to wrap all UI code with HAVE_LIBNCURSES. + + // TODO Load this, and all others from .taskrc to override the defaults. + logSetDirectory ("."); + logSetName ("task"); + logDuplicates (false); + logEnable (false); + + try + { + // TODO Ick. Clean this up. + std::string prefix = " (vertical (horizontal 2 (panel title *) (panel stats 22)) (horizontal 1 (panel message 60%) (panel clock *)) "; + std::string suffix = " (panel keys 2)))"; + + std::string layout_def = "(layout default" + prefix + "(panel report *)" + suffix; + std::string layout_report_stats = "(layout report.stats" + prefix + "(panel report.stats *)" + suffix; + + UI ui; // Construct a UI coordinator. + createLayout (ui, "default", layout_def); + createLayout (ui, "report.stats", layout_report_stats); + + ui.interactive (); // Start ncurses, event loop. + } + + // TODO Integrate regular task error handling, using Context::debug. + catch (int e) { std::cout << e << std::endl; } + catch (const char* e) { std::cout << e << std::endl; } + catch (std::string& e) { std::cout << e << std::endl; } + catch (...) { std::cout << "Unknown error." << std::endl; } + + logWrite ("---"); + +#else + + throw stringtable.get (INTERACTIVE_NO_NCURSES, + "Interactive task is only available when built with ncurses " + "support."); + +#endif + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +void Context::createLayout ( + UI& ui, + const std::string& name, + const std::string& def) +{ + // A layout named 'foo' can be overridden with a .taskrc config variable that + // is named 'layout.foo'. + std::string definition = config.get ("layout." + name); + if (definition == "") + definition = def; + + Layout* l = new Layout (definition); + ui.add (name, l); +} + +//////////////////////////////////////////////////////////////////////////////// +int Context::getWidth () +{ + // Determine window size, and set table accordingly. + int width = config.getInteger ("defaultwidth"); + +#ifdef HAVE_LIBNCURSES + if (config.getBoolean ("curses")) + { + initscr (); + width = COLS; + endwin (); + + std::stringstream out; + out << "Context::getWidth: ncurses determined width of " << width << " characters"; + debug (out.str ()); + } + else + debug ("Context::getWidth: ncurses available but disabled."); +#else + std::stringstream out; + out << "Context::getWidth: no ncurses, using width of " << width << " characters"; + debug (out.str ()); +#endif + + return width; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/log.cpp b/src/ui/log.cpp new file mode 100644 index 000000000..b65160cfb --- /dev/null +++ b/src/ui/log.cpp @@ -0,0 +1,217 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// Global data. +static std::string gLogDir = "."; +static std::string gLogName = "default"; +static bool gEnabled = true; +static const unsigned int maxMessageLength = 128; + +static bool bDuplicates = false; +static std::string gPrior = "none"; +static int gRepetitionCount = 0; + +//////////////////////////////////////////////////////////////////////////////// +void logSetDirectory (const std::string& dir) +{ + gLogDir = dir; +} + +//////////////////////////////////////////////////////////////////////////////// +void logSetName (const std::string& name) +{ + gLogName = name; +} + +//////////////////////////////////////////////////////////////////////////////// +void logEnable (const bool value) +{ + gEnabled = value; +} + +//////////////////////////////////////////////////////////////////////////////// +void logDuplicates (const bool value) +{ + bDuplicates = value; +} + +//////////////////////////////////////////////////////////////////////////////// +void getTimeStamp (std::string& ts) +{ + // Get time info. + time_t now; + time (&now); + struct tm* t = localtime (&now); + + // Generate timestamp. + char timestamp[20]; + sprintf (timestamp, "%04d-%02d-%02d %02d:%02d:%02d", + t->tm_year + 1900, + t->tm_mon + 1, + t->tm_mday, + t->tm_hour, + t->tm_min, + t->tm_sec); + + ts = timestamp; +} + +//////////////////////////////////////////////////////////////////////////////// +// #Version: 1.0 +// #Date: 17-Oct-2004 +// #Fields: date time x-pid x-message +// 2004-10-18 00:12:00 12345 "Message" +void logWrite (const std::string& message) +{ + if (!gEnabled) return; + + if (!bDuplicates) + { + if (message == gPrior) + { + ++gRepetitionCount; + return; + } + else + gPrior = message; + } + + // Get time info. + time_t now; + time (&now); + struct tm* t = localtime (&now); + + // Sanitize 'message'. + std::string sanitized = message; + std::string::size_type bad; + while ((bad = sanitized.find ("\"")) != std::string::npos) + sanitized.replace (bad, 1, "'"); + + if (sanitized.length () > maxMessageLength) + sanitized = sanitized.substr (0, maxMessageLength) + "..."; + + // Generate timestamp. + char timestamp[20]; + sprintf (timestamp, "%04d-%02d-%02d %02d:%02d:%02d", + t->tm_year + 1900, + t->tm_mon + 1, + t->tm_mday, + t->tm_hour, + t->tm_min, + t->tm_sec); + + // Determine file name. + char file[_POSIX_PATH_MAX]; + sprintf (file, "%s/%s.%04d%02d%02d.log", + gLogDir.c_str (), + gLogName.c_str (), + t->tm_year + 1900, + t->tm_mon + 1, + t->tm_mday); + + // Determine whether file exists. + bool exists = access (file, F_OK) != -1 ? true : false; + + // Create file if necessary, with header. + FILE* log = fopen (file, "a"); + if (log) + { + // Lock file. +#if defined (__SVR4) && defined (__sun) +#else + int fn = fileno (log); + if (! flock (fn, LOCK_EX)) + { +#endif + // Create the header, if the file did not exist. + if (! exists) + { + fprintf (log, "# File: %s.%04d%02d%02d.log\n", + gLogName.c_str (), + t->tm_year + 1900, + t->tm_mon + 1, + t->tm_mday); + fprintf (log, "# Fields: date time x-pid x-message\n"); + } + + // Optionally write a repetition message. + if (gRepetitionCount) + { + fprintf (log, "%s %d \"(Repeated %d times)\"\n", + timestamp, + (int) getpid (), + gRepetitionCount); + gRepetitionCount = 0; + } + + // Write entry. + fprintf (log, "%s %d \"%s\"\n", + timestamp, + (int) getpid (), + sanitized.c_str ()); +#if defined (__SVR4) && defined (__sun) +#else + } +#endif + + // close file. + fclose (log); + + if (! exists) + chmod (file, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void logWrite (const char* message, ...) +{ + if (!gEnabled) return; + + // Crude and mostly ineffective check for buffer overrun. + if (::strlen (message) >= 65536) + throw std::string ("Data exceeds 65,536 bytes. Break data into smaller chunks."); + + char buffer[65536]; + va_list args; + va_start (args, message); + vsnprintf (buffer, 65536, message, args); + va_end (args); + + logWrite (std::string (buffer)); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/log.h b/src/ui/log.h new file mode 100644 index 000000000..4dcceff4f --- /dev/null +++ b/src/ui/log.h @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_LOG +#define INCLUDED_LOG + +#include + +void logSetDirectory (const std::string&); +void logSetName (const std::string&); +void logEnable (const bool); +void logDuplicates (const bool); +void getTimeStamp (std::string&); +void logWrite (const std::string&); +void logWrite (const char*, ...); + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/ui/resize.cpp b/src/ui/resize.cpp new file mode 100644 index 000000000..607ddaaa6 --- /dev/null +++ b/src/ui/resize.cpp @@ -0,0 +1,284 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// 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 +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include "Rectangle.h" +#include "Tree.h" +#include "Lisp.h" + +static void recalcNode (Tree*, int, int, int, int); +static void distribute (const int, const std::vector &, std::vector &); +static void getRequests (Tree*, std::vector &); + +//////////////////////////////////////////////////////////////////////////////// +void resize ( + const std::string& input, + const int width, + const int height, + std::string& name, + std::map & panels) +{ + // Parse the input into a tree. + Lisp l; + Tree* root = l.parse (input); + + // Navigate the tree and calculate sizes. This requires a breadth-first + // traversal, so that calculations at one level are then propagated down. + // After all, the window dimensions dictate the layout size, and so on down. + Tree* layout = (*root)[0]; + layout->attribute ("left", 0); + layout->attribute ("top", 0); + layout->attribute ("width", width); + layout->attribute ("height", height); + recalcNode (layout, 0, 0, width, height); + + // Extract the name of the layout. + std::vector tags = layout->allTags (); + if (tags.size () >= 2) + name = tags[1]; + else + name = "anonymous"; + + // Extract panels. + panels.clear (); + std::vector nodes; + layout->enumerate (nodes); + std::vector ::iterator it; + for (it = nodes.begin (); it != nodes.end (); ++it) + { + tags = (*it)->allTags (); + if (tags[0] == "panel") + panels[tags[1]] = + Rectangle ( + atoi ((*it)->attribute ("left").c_str ()), + atoi ((*it)->attribute ("top").c_str ()), + atoi ((*it)->attribute ("width").c_str ()), + atoi ((*it)->attribute ("height").c_str ())); + } + + delete root; +} + +//////////////////////////////////////////////////////////////////////////////// +// Specified node has associated width and height. Subdivide either width or +// height across all tree branches depending on which container the parent is. +static void recalcNode (Tree* parent, int left, int top, int width, int height) +{ + // What kind of parent is this? layout, horizontal or vertical? + std::string parent_type = "?"; + std::vector parent_tags = parent->allTags (); + if (parent_tags.size ()) + parent_type = parent_tags[0]; + else + throw std::string ("Error: node has no specified type"); + + if (parent_type == "horizontal") + { + std::vector requested; + getRequests (parent, requested); + + std::vector allocated; + distribute (width, requested, allocated); + + int current_left = left; + for (int i = 0; i < parent->branches (); ++i) + { + (*parent)[i]->attribute ("left", current_left); + (*parent)[i]->attribute ("top", top); + (*parent)[i]->attribute ("width", allocated[i]); + (*parent)[i]->attribute ("height", height); + current_left += allocated[i]; + } + } + + else if (parent_type == "vertical") + { + std::vector requested; + getRequests (parent, requested); + + std::vector allocated; + distribute (height, requested, allocated); + + int current_top = top; + for (int i = 0; i < parent->branches (); ++i) + { + (*parent)[i]->attribute ("left", left); + (*parent)[i]->attribute ("top", current_top); + (*parent)[i]->attribute ("width", width); + (*parent)[i]->attribute ("height", allocated[i]); + current_top += allocated[i]; + } + } + + else if (parent_type == "layout") + { + if (! (*parent)[0]) + throw std::string ("Error: layout has no contents."); + + (*parent)[0]->attribute ("left", left); + (*parent)[0]->attribute ("top", top); + (*parent)[0]->attribute ("width", width); + (*parent)[0]->attribute ("height", height); + } + + // Now recurse to each branch of parent that is a container. + for (int i = 0; i < parent->branches (); ++i) + { + Tree* child = (*parent)[i]; + if (child->hasTag ("horizontal") || + child->hasTag ("vertical")) + { + recalcNode (child, + atoi (child->attribute ("left").c_str ()), + atoi (child->attribute ("top").c_str ()), + atoi (child->attribute ("width").c_str ()), + atoi (child->attribute ("height").c_str ())); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// If the call looks like this: +// distribute (60, [10, *, *, *, *, *, 50%], []) +// +// Then the result is: +// distribute (60, [10, *, 50%], [10, 5, 5, 5, 5, 5, 25]) +// +// A literal number is a request. It will be granted, provided there is space. +// A percentage is a request for a portion of the unallocated amount. +// A wildcard is a request for an equal share, among all wildcards, of the +// unallocated amount. +// +static void distribute ( + const int total, + const std::vector & input, + std::vector & output) +{ + int allocated = 0; + int unallocated = total; + + output.clear (); + + // First count up the requested. + for (unsigned int i = 0; i < input.size (); ++i) + if (input[i] == "*" || input[i].find ("%") != std::string::npos) + output.push_back (0); + else + { + int value = atoi (input[i].c_str ()); + output.push_back (value); + allocated += value; + unallocated -= value; + } + + if (allocated > total) + throw std::string ("Error: over allocation by request."); + + // Now include the proportional. + int before_allocated = allocated; + for (unsigned int i = 0; i < input.size (); ++i) + { + if (input[i].find ("%") != std::string::npos) + { + int value = atoi (input[i].c_str ()); + value = (value * unallocated) / 100; + output[i] = value; + + allocated += value; + } + } + + unallocated -= (allocated - before_allocated); + + if (allocated > total) + throw std::string ("Error: over allocation by request."); + + // Count the wildcards. + int wildcards = 0; + for (unsigned int i = 0; i < input.size (); ++i) + if (input[i] == "*") + ++wildcards; + + // Evenly distribute unallocated among the wildcards. + for (unsigned int i = 0; i < input.size (); ++i) + { + if (input[i] == "*") + { + if (wildcards > 1) + { + int portion = unallocated / wildcards; + --wildcards; + + output[i] = portion; + + allocated += portion; + unallocated -= portion; + } + else + { + output[i] = unallocated; + allocated += unallocated; + unallocated = 0; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +static void getRequests (Tree* node, std::vector & requested) +{ + requested.clear (); + + for (int i = 0; i < node->branches (); ++i) + { + Tree* child = (*node)[i]; + std::vector child_tags = child->allTags (); + + switch (child_tags.size ()) + { + case 1: // (xcontainer (...)) + requested.push_back ("*"); + break; + + case 2: // (xcontainer size (...)) + requested.push_back (child_tags[1]); + break; + + case 3: // (panel name size) + requested.push_back (child_tags[2]); + break; + + default: + throw std::string ("Error: unexpected number of tags in a node"); + break; + } + } +} + +////////////////////////////////////////////////////////////////////////////////