Compare commits

...

189 Commits
2.0 ... main

Author SHA1 Message Date
ee087d302d Merge timestamper-gui 2024-08-04 21:42:39 -05:00
5b24dac2b8 Move timestamper-gui into a subfolder. 2024-08-04 21:41:47 -05:00
b6dc3eb98c Merge branch 'temp' 2024-08-04 21:23:13 -05:00
ea3f554224 Move time-analyzer into it's own directory in preparation to merge into the main timestamper project. 2024-08-04 21:22:38 -05:00
111be977c6 Move web application into /web. 2024-08-04 20:48:32 -05:00
338eab1c96 Merge Web application. 2024-08-04 20:45:13 -05:00
fbc9052e5a Move CLI application code into /cli. 2024-08-04 20:44:46 -05:00
0a49df1828 Merge CLI project. 2024-08-04 20:42:37 -05:00
e32c6358e9 Move library code into lib folder in preparation for consolidating the lib, CLI, and web projects. 2024-08-04 20:42:26 -05:00
Jonathan Bernard
168d7cd03f Moved to gradle. FIXME: This is broken.
The JDB Util dependency has been upgraded to 3.4 from 2.0. This includes
backwords-incompatible changes in the LightOptionParser class used by this
project. I need to re-write that code to use the newer version of
LightOptionParser.
2015-02-05 00:54:06 -06:00
Jonathan Bernard
66c00e2473 Moved to gradle build system. 2015-02-05 00:41:26 -06:00
Jonathan Bernard
3cb9bfc228 Updated .gitignore to ignore new build file name. 2015-01-13 11:57:16 -06:00
Jonathan Bernard
950f4a0b70 Added DEV build target and configuration. 2015-01-13 11:55:32 -06:00
Jonathan Bernard
12bae10384 New entry field auto-focusses. Bugfix. 2015-01-13 11:55:10 -06:00
Jonathan Bernard
7ae584bec9 Cleaning up whitespace. 2014-12-22 09:58:24 -06:00
Jonathan Bernard
0da15ef102 Stubs for WebSocket push notifications. 2014-12-22 09:57:31 -06:00
Jonathan Bernard
bb713abeed Refresh entry on save. Fixed Ctrl-Enter detection. 2014-12-22 09:55:56 -06:00
Jonathan Bernard
3f21f62889 Updated yaws_api.hrl to the version from Yaws 1.98 2014-07-28 08:30:27 -05:00
Jonathan Bernard
dc70b00403 Bugfix, started working on mobile UI.
* Strted making the page responsive (size-based media queries, mobile meta
  tags, and reworked the UI for small sizes).
* Fixed bugs in the periodicRefresh function.
* Changed the yaw.prod.conf file to match the actual PROD coniguration.
2014-03-30 03:37:59 +00:00
Jonathan Bernard
54eb4fdafa Fixed a bug in the makefile: missing the z flag to tar. 2014-03-30 03:36:06 +00:00
Jonathan Bernard
59c9544ab9 Updated test database to match latest schema. 2014-03-30 03:33:46 +00:00
Jonathan Bernard
846f505f6b Bugfix to periodic refresh behavior. 2013-11-13 10:32:42 -06:00
Jonathan Bernard
de405f01f1 Prevent the current mark from rerendering when the user is editing it. 2013-11-04 03:55:41 +00:00
Jonathan Bernard
4a7486305c Fixed bug in options definition. 2013-10-26 10:09:01 -05:00
Jonathan Bernard
26c5435b5c Makefile now creates build.tar.gz 2013-10-25 16:00:43 +00:00
Jonathan Bernard
839646b154 Fixed Session timeouts. 2013-10-25 15:47:20 +00:00
Jonathan Bernard
e8bebb49fb UI and build process tweaks.
* Reworked the buid process to compile SCSS files and move WWW assets
  directories individually.
* Added pulsing animation for the current marker.
* Bugfix for View objects.
2013-10-24 20:42:53 +00:00
Jonathan Bernard
c42a3805c2 Fixed UI surrounding day separators.
* Reworked separator display. Now it is handled by the
  `EntryListView.renderOne` function instead of `EntryListView.render`. This
  way the proper separator is inserted even if we are only adding one new entry
  (like when creating a new entry) and not only when we refresh the list.
* Added a periodic refresh function that is triggered every minute. It
  refreshes the current entry so the duration is current, and refreshes te main
  list when a new day begins.
2013-10-24 14:55:08 +00:00
Jonathan Bernard
f4eeb91d1a Fixed deploy target, new entry creation.
* Modified make target `deploy` to require the `build` target first.
* When creating a new timestamp entry we no longer refresh the collection from
  the server. Now we use the `success` callback to set the server-supplied
  values on the model.
2013-10-22 16:46:34 +00:00
Jonathan Bernard
a3c55e918e Bugfixes: session management, new entry creation.
* Changed the cookie Path value to allow the cookie to be reused for the
  domain, not just ths `/ts_api` path. This allows the user to refresh the page
  and reuse their existing session as long as it is not stale.
* Fixed a bug in the `ts_json:ejson_to_record_strict/2` function. It was
  expecting a record out of `ts_json:ejson_to_record/2` but that function
  returns a tuple with the record and extended data. Because of the way
  `ejson_to_record_strict` uses case statements to check for specific values it
  was still passing the parsed record and data through, but all the checks were
  being bypassed.
* Fixed bugs in the `index.yaws` bootstrap code for the case where the user
  already has a valid session.
* Added `urlRoot` functions to the Backbone model definitions.
* Changed the behavior of the new entry creation method. We were trying to
  fetch just updated attributes from the server for the model we created, but
  we were pulling all the entries due to the URL backbone was using. This led
  to the new client-side model having all the previous entry models as
  attributes. Ideally we would fix the fetch so that only the one model is
  requested from the server, but we run into a catch-22 because the lookup id
  is not know by the client as it is generated on the server-side. For now I
  have changed this behavior so that we still pull all entries, but we pull
  them into the collection. The collection is then smart enough to update the
  entries that have changed (namely the new one). The server returns the newly
  created entry attributes in response to the POST request that the client
  makes initially, so when I have more time to work on this I plan to do away
  with the fetch after create, and just pull in the data from the server's
  response.
* Changed formatting.
2013-10-22 15:32:22 +00:00
Jonathan Bernard
0278179452 Extended the session timeout to 6 hours. 2013-10-15 14:26:38 +00:00
Jonathan Bernard
8cc257c24e Fixed bugs in initial 2.0 release. 2013-10-11 16:37:11 -05:00
Jonathan Bernard
216b3c5303 Updated to library 2.1. Fixed auto-generated start script. 2013-10-11 16:34:36 -05:00
Jonathan Bernard
774778ee55 Added UUID binary. 2013-10-11 20:45:45 +00:00
Jonathan Bernard
98032e2b89 Added UUIDs to timestamps, refactored the build process
* Added UUIDs to `ts_entry` records. Updated `ts_json:construct_record` to
  respond to `uuid` member properties if present. UUIDs are not required by the
  strict parsing functions in `ts_json` because the client will make a request
  with no UUID if it is a purely new timestamp. IN fact, this is the normal
  case. The UUID is only present when another tool is syncing its copy of this
  timeline wand adding entries that it has created and assigned UUIDs to.
* `ts_entry:new` will create a UUID for a new entry if it does not already have
  one.
* Restructured the build process to put all build artifacts into a dedicated
  `build` subdirectory, instead of mising them in an amongst the source code.
* Added the `uuid` module to the project. It can be found at

      https://gitorious.org/avtobiff/erlang-uuid

* Rewrote asset URLs to use relative paths instead of absolute paths. Relative
  paths are correct in this case, becuase assets always live alongside the HTML
  pages. This change was needed to accomodate the new organization of the JDB
  Labs dev environment, where all projects live under subdirectories of the
  same virtual server instead of subdomains.
* Tweaked the timestamp entry fields in the web UI to save when the field is
  blurred, not just when <Enter> or <Ctrl>-<Enter> is pressed (though those
  still work).
2013-10-11 20:06:31 +00:00
Jonathan Bernard
d473d74112 Updated lib version 2.0 and promoted CLI to 1.0
`timestamper-lib-java` introduces a breaking change in the way timeline files
are written, so this is considered a new major version change.
2013-10-11 20:04:12 +00:00
Jonathan Bernard
9b357359b6 Rewrote resource URLs to use the page protocol. 2013-09-22 21:48:56 +00:00
Jonathan Bernard
15750f65d4 Upgrading timestamper-lib version. 2013-09-22 15:23:15 -05:00
Jonathan Bernard
bc51d17932 Upgrading to Griffon 1.2.0 2013-09-22 15:22:44 -05:00
Jonathan Bernard
621e00deaa Using lists instead of atoms for things being turned into JSON. 2013-09-21 15:52:29 -05:00
Jonathan Bernard
100ca8fd74 Added SSL, CORS support for the API. 2013-09-21 17:19:13 +00:00
Jonathan Bernard
1e05258381 Bugfixes.
* ts_api:list_timelines/2 was looking for keys as atoms instead of lists (strings).
* Exported the ts_json:decode_datetime/1 function (needed by ts_api module).
* Fixed a crash case when trying to check the credentials of a non-existent user.
2013-09-15 00:33:02 +00:00
Jonathan Bernard
e296e87003 Version 1.6: Added help section, bugfix, updated build process.
* Build now includes config and bin resources.
* Build new builds an executable jar.
* Added help section.
* Bugfix for working directory.
2013-09-13 09:16:08 -05:00
Jonathan Bernard
269a9b9125 Version 0.5: Added support for syncing timelines.
* Added missing libraries required by timeline-lib to support syncable
  timelines.
* Added code to shutdown sync threads upon exit.
2013-09-13 08:57:45 -05:00
Jonathan Bernard
38dc560686 Updated yaws_api.hrl, prod configuration (to live alongside VBS). 2013-09-13 01:56:45 +00:00
Jonathan Bernard
76bf676c2c Added edit and delete commands.
* Added `edit` which uses the program in $EDITOR to allow the user to edit the
  current timeline marker.
* Added `delete` to remove the current timeline marker from the timeline.
* Added support for using a named TTY device in order to use an interactive
  program ($EDITOR in this case) to take over interaction with the user. There
  is still a problem with this, in that a process by default only has access to
  its controlling TTY device. This mean, for example, that redirecting the
  subprocess input and output to the TTY will fail to work properly if the
  timestamper CLI process is not part of the same process group as the process
  owning the TTY the user is interacting with. This is the case when using
  nailgun: the java process running TimeStamperCLI is not part of the same
  process group as the user's client shell.

  I think TTY device permissions may be alterable, and we can work around this
  by changing the permissions for the current TTY in the launcher script that
  invokes the nailgun client. Needs more investigation.
* Added package build target to create a zip of the CLI standalone installation.
2013-08-10 01:40:02 -05:00
Jonathan Bernard
284a4159d1 Added command to re-read the timeline source. 2013-08-09 11:45:56 -05:00
Jonathan Bernard
e4a756baf5 Moved back to realtime update model.
* Using patched version of Jansi.
* Raw ANSI controls always sent to play well with Nailgun.
2013-08-08 23:14:18 -05:00
Jonathan Bernard
f78cd57ec7 Can now add marks. Display is single-threaded, refresh on command.
Also implemented save and quit (including cases for Ctrl-D, `exit`, and `quit`)
2013-08-08 20:09:15 -05:00
Jonathan Bernard
769245b690 Working on UI using ANSI escape sequences. 2013-08-08 14:21:00 -05:00
Jonathan Bernard
f9e700f27b Updated common-build to 1.10 2013-07-30 11:30:43 -05:00
Jonathan Bernard
00a5252542 Small changes to Category and Event toString()
* Updated Category and Event toString methods to report the duration alongside
  the description.
* Updated QD TicketCategorizationPlan to reflect previous changes to
  CategorizationPlan interface.
2012-09-04 10:41:40 -05:00
Jonathan Bernard
5dff4de089 Support for weekly summaries and daily analysis.
build.xml
---------

Added `build-shell` target. This creates a folder, `build/shell` and copies all
of the required libraries and class files to that folder, excluding the
groovy-all jar that conflicts with groovysh's internal classpath.

Category.groovy
---------------

Fixed a bug in the `addEvent()` function.

CategorizationPlan.groovy
-------------------------

* Rewrote this as groovy code.
* Now is an abstract class instead of an interface.
* Added a property, `newCatSetupFun`, which is a closure that will be run
  whenever this categorization plan creates a new category. A caller can set
  this via the constructor.
* Added a function, `setupNewCategory()` that should be called inside of all
  `newCategory()` implementations. This allows the user to have categorization
  plans added to the subcategories dynamically.
* Updated all the CategorizationPlan implementations to respect this new
  behavior.

start-script.groovy
-------------------

Implemented a more comprehensive analysis.
2012-09-01 22:28:59 -07:00
Jonathan Bernard
2650fca7f1 Removed unnecessary dependancy. 2012-08-30 22:50:52 -05:00
Jonathan Bernard
10677e5705 Migrated to jdb-build-1.9, documentation. 2012-08-30 06:33:35 -07:00
Jonathan Bernard
d076b739f5 Reorganized the code into new packages. 2011-10-27 20:14:08 -05:00
Jonathan Bernard
5b2ecf7b65 Added libraries required to support lib-java 1.1. 2011-06-27 17:22:43 -05:00
Jonathan Bernard
cda3544cac Creating initial project structure. 2011-06-19 07:23:36 -05:00
Jonathan Bernard
b01421d45d Adjusted session timeout to be 12 hours. 2011-06-18 09:36:03 -05:00
Jonathan Bernard
f3ef7db088 Work on front-side UI.
* Resolved D0003: Add day separators.

    * Created ``daySeparatorTemplate`` for ICanHaz templating.
    * Refactored ``EntryListView.render`` to spit out day separators in between
      days on the timeline. Fixed the "Today" separator to the top of the entry
      container. We no longer prepend entries to the entry container, we now put
      them after the top separator in the entry container.
    * Created ``EntryListView.formatDaySeparator`` to format the labels
      according to the relative time to today: "Yesterday", "Last Monday",
      "Friday, May 28" for example.
    * Removed the cludgier ``toWords``, ``daysApart``, and `` capitalize``
      functions that were going to serve the same purpose.
* Resolved D0021: Constrain notes width to mark.
2011-06-17 16:11:18 -05:00
Jonathan Bernard
81503112a8 Work on day separators.
* Overrode ``EntryModel.{get,set}`` to return a ``Date`` object and allow you to
  set the value with either a ``Date`` object, or the JSON date string.
* Updated code to reflect the ``EntryModel.timestamp`` data change.
* Added ``daysApart`` to calculate calendar days between given ``Date``s
* Added ``getEnglishDate`` to return a date description relative to the current
  day ("Today", "Last year" for example).
2011-06-17 16:11:12 -05:00
Jonathan Bernard
1fff94b622 Implemented entry exclusions in UI.
* Resolved D0020: Add exclusion filter for entries.
* Refactored ``timeline`` fields of several objects in ``ts.js`` to be
  ``timelineModel`` to be explicit.
* Added support for exclusions in ``EntryListView.renderOne`` and
  ``EntryListView.render``.
2011-06-16 17:55:01 -05:00
Jonathan Bernard
98071f270d Refactored code packages, com.jdbernard -> com.jdblabs
* Updated contact info, package declarations, and config to match.
* Removed com.jdbernard.timestamper.core, it is becoming a seperate library
  project.
2011-06-16 12:33:54 -05:00
Jonathan Bernard
602bdca7e3 Minor fixes needed for last_timeline to work.
* Added route back for PUT /ts_api/user/<username>
* Fixed typo in ``ts_api:put_user/2``.
* Fixed incorrect expected return from ``ts_ext_data:set_property/3``.
* Added missing assignment for ``ts_user.email`` in the appropriate
  ``ts_json:construct_record/3`` clause.
* Fixed model attribute assignment in ``ts.js``.
2011-06-15 18:31:53 -05:00
Jonathan Bernard
cd182d54c3 Fixing problems introduced by the model change.
* Many calls to ``ts_ext_data:get_properties/1`` from ``ts_api`` were passing
  the record reference, not the record itself. Fixed.
* Created ``ts_api:put_user/2`` which updates a ``ts_user`` record. This is
  needed specifically for updating the ``liat_timeline`` extended data property.
* Removed unreachable code in ``ts_api:post_entry/3``
* Fixed return value of some ``ts_user`` functions to use the
  ``{Status, Value}`` convention. TODO: make sure all calls are using the same
  convention.
* Fixed error message formatting in ``ts_ext_data:set_property/3``.
* Added clauses to ``ts_json:ejson_to_record/2``,
  ``ts_json:ejson_to_record_strict/2`` and ``ts_json:construct_record/3`` to
  handle the ``ts_user`` record type.
* Added code to ``AppView.loadInitialData`` and ``AppView.selectTimeline`` to
  support the ``last_timeline`` extended data property.
2011-06-15 16:59:00 -05:00
Jonathan Bernard
3999e736be Bug fixes and implementation refinement around `ts_ext_data`.
* Transformed the test database to match the new data model. Added
  ``ts_ext_data`` table and moved ``ts_user.ext_data`` values to it.
* Added D0022: cascade delete ``ts_ext_data`` when ``ts_entry`` record is
  deleted.
* Completed refactor of ``ts_api`` functions to account for extended data:

    * ``post_entry/3``
    * ``put_entry/3``

* Created ``ts_entry:write/2`` to write extended data atomically with the entry.
* Fixed copy/paste bug in ``ts_ext_data:create_table/1``.
* Fixed implementation of ``ts_ext_data:set_property/3`` to be explicit about
  taking a record, not a reference. It then extracts the reference and passes it
  to the underlying implementation in ``ts_ext_data:do_set_property/3``.
* Refactored ``ts_ext_data:do_set_property/3`` to take a record *reference*, not
  the record itself.
* Refactored the ``ts_ext_data:get_property/2`` and
  ``ts_ext_data:get_properties/1`` functions to return key value tuple pairs,
  not ``ts_ext_data{}`` records.
2011-06-15 07:34:41 -05:00
Jonathan Bernard
658091e947 Bugfixing, compile-error-sqhashung. 2011-06-15 00:54:58 -05:00
Jonathan Bernard
7d11112226 Fixed extended data handling in `ts_json` module.
* Refactored ``ts_json:ext_data_to_ejson{1,2}`` from a function that iterates
  over a list of extended data properties and returns a transformed list to
  ``ts_json:ext_data_to_ejson/1``, a function that transforms one extended data
  property. The iteration behavior is acheived using ``lists:map/2``.
* Refactored ``ts_json:ejson_to_ext_data{1,2}`` in a similar fashion to
  ``ts_json:ext_data_to_ejson{1,2}``.
* Fixed ``ts_json:construct_record/3`` to use ``ts_json:ejson_to_ext_data/1``.
2011-06-15 00:26:31 -05:00
Jonathan Bernard
8bb50c058d Changed Data Model
------------------

* Created the extended data table. This is a more generic version of the
  extended data field that was on ``ts_user``. Instead of arbitrary key-value
  pairs going in a list on the user record we will have an additional table, a
  one to many relationship between existing tables and the ``ts_ext_data``
  table, each row being an extended value of the corresponding record.
* Added support to the ``timestamper:create_tables/1`` and
  ``timestamper_dev:create_table/1`` functions for the new ``ts_ext_data`` table.

Documentation
-------------

* Added some general docs about the DB layer code.
* Added two new issues, *D0020*: Entry exclusion filters and *D0021*: notes width.

API Changes
-----------

Necessitated by the change to the data model.

* Updated ``ts_api`` data-retrieval functions to use extended data:

    * ``get_user_summary/2``
    * ``list_timelines/2``
    * ``list_entries/3``

* Updated ``ts_api:put_timeline/3`` to parse the extended data supplied by the
  caller. *FIXME* it is not actually saving this data.
* TODO: Started updating ``ts_api:post_entry/3`` to handle extended data, need
  to finish.

Database Layer
--------------

Changes necessitated by the change to the model.

* Added ``ts_common:do_set_ext_data/2`` which iterates through the extended data
  key-value pairs calling ``ts_ext_data:set_property/3``. It does not provide a
  transaction context.
* Split ``ts_common:new/1`` and ``ts_common:update/1`` functions into multiple
  functions to prevent code-duplication:

    * ``do_{new,update}`` contains the code that performs integrity checks and
      the actual database write function. It does this assuming that it is being
      called from within the context of an mnesia transaction (uses
      ``mnesia:read`` and ``mnesia:write``).
    * ``{new,update}/1`` performs the same function as previously. The
      implementation changed from using mnesia ``dirty_*`` calls to prociding a
      transaction and calling ``do_{new,update}/1``.
    * ``{new,update}/2`` expect the record to update/create and the extended
      data to write atomically with the record. They provide a transaction
      context, call ``do_{new,update}/1``, then call ``do_set_ext_data/2``.

* Similar to the refactoring of ``ts_common:{new,update}/1``,
  ``ts_entry:{new,update}/1`` have been refactored into multiple methods each to
  support extended data properties:

    * ``do_{new,update}/1`` perform the actual update assuming we have already
      established an mnesia transaction.
    * ``{new,update}/1`` behave the same as they used to, but now do so by
      creating an mnesia transaction and calling ``do_{new,update}/1``.
    * ``{new,update}/2`` create an mnesia transaction, call
      ``do_{new,update}/1``, and then call ``ts_common:do_set_ext_data/2``.

* Again similar to the refactoring of ``ts_common:{new,update}/1``,
  ``ts_user:{new,update}/1`` have been refactored into multiple methods each to
  support extended data properties:

    * ``do_{new,update}/1`` perform the actual update assuming we have already
      established an mnesia transaction.
    * ``{new,update}/1`` behave the same as they used to, but now do so by
      creating an mnesia transaction and calling ``do_{new,update}/1``.
    * ``{new,update}/2`` create an mnesia transaction, call
      ``do_{new,update}/1``, and then call ``ts_common:do_set_ext_data/2``.

* Created the ``ts_ext_data`` module as the interface to the extended data
  properties introduced in the data model:

    * ``create_table/1`` performs the same function as it does in the other db
      layer modules, creates the table with the appropriate structure given the
      more general table options desired (location, storage type, etc.).
    * ``set_property/3`` takes a record, a property key, and a property value as
      input and sets the property described by the property key on the record to
      the given value, assuming this is a valid property for the record to have.
      For example, currently the ``ts_user`` record can have an associated
      property, ``last_timeline``, which represents the last timeline the user
      was working with. Trying to pass this property with a ``ts_entry`` record
      would result in an exception. This function uses
      ``ts_ext_data:do_set_property/3`` as its underlying implementation.
    * ``get_property/2`` takes a record and a property key and returns the value
      of that property for the given record, or ``not_set`` if the property has
      not been set on that record. This method creates its own mnesia
      transaction.
    * ``get_properties/1`` takes a record and returns a list of key-value tuples
      representing all of the extended data properties set for the given record.
      This method creates its own mnesia transaction.
    * ``do_set_property/3`` takes a record reference (not the whole record), a
      a property key, and adds the property assignment to the ``ts_ext_data``
      table. It creates its own mnesia transaction.

* Added ``new/2`` and ``update/2`` to the ``ts_timeline`` module to support
  extended data properties. They delegate implementation to
  ``ts_common:{new,update}/2``.

JSON Encoding/Decoding
----------------------

Changes necessitated by the change to the data model.

The JSON objects now contain a potentially unlimited number of fields, as each
extended data property is encoded as a seperate field, and looks no different
from any of the required fields on the object. The intended explanation in API
documentation is that each object type (``user``, ``timeline``, or ``entry``)
now has both *required* fields that *MUST* be present in every message in either
direction and *optional* fields that may or may not be present in any
communication with the API. There should be a clear distinction between which
fields are required and which are optional. It might also be a good idea to
provide a suggested default for optional values when they are not present.

* Updated documentation about JSON record structures to reflect the fact that
  there are now potentially many optional attributes in addition to the required
  attributes for each record.
* ``record_to_ejson/1`` refactored to ``record_to_ejson/2`` which also takes
  the extended data attributes and appends them as additional attributes to
  the end of the record structure.
* Created ``ext_data_to_ejson/{1,2}`` to provide a mechanism for reformatting
  extended data properties whose internal representations are not immediately
  translatable into JSON. Currently only the ``entry_exclusions`` property,
  which is a list of strings, needs to be treated this way (changing from
  ``[val, val]`` to ``{array, [val, val]}`` as needed by ``json:encode/1``.
  ``ext_data_to_ejson/1`` acts as a more user-friendly facade to
  ``ext_data_to_ejson/2``.
* Rewrote ``ejson_to_record/{2,3}`` and ``ejson_to_record_strict/{2,3}`` to
  handle extended data. They now use a common method, ``construct_record/3`` to
  create the actual record object and extended data key-value list.
  ``ejson_to_record_strict/{2,3}`` only differs in that it checks for the
  presence of each required field of the record after the record is constructed.
  The three-parameter versions of these functions also take in the intended
  reference for the constructed record, replacing anything that is in the EJSON
  body as the record reference (useful when the body does not have the record
  ids). These methods now return a tuple: ``{Record, ExtData}`` instead of just
  the record.
* Created ``construct_record/3`` takes a record and the EJSON fields from the
  input object. The third parameter is an accumulator for the extended data
  properties found when constructing the record. This method works by iterating
  over the list of input fields. It recognizes any required fields and updates
  the record being built with the value. Any fields it does not recognize it
  assumes are extended data properties and adds to its list. When all input
  fields have been visited it returns the record and list it has constructed.
* ``ejson_to_ext_data/{1,2}`` is the inverse of ``ext_data_to_ejson/{1,2}``.
  *TODO*: this method is not actually being used by the ``ejson_to_record*``
  methods.
2011-06-14 16:48:04 -05:00
Jonathan Bernard
99d04935cb Implemented timeline selection.
* Added personal VIM ide extension.
* Implemented timeline selection (resolves D0007)
* Slightly restyled the new timeline button and timeline list menu.
2011-06-10 11:49:45 -05:00
Jonathan Bernard
b5eadd6fc4 Fixed timeline creation, API logical fixes.
* Resolved issues:

    * #0006: Fix timeline menu UI.
    * #0015: Create new timeline button in timeline menu.
    * #0017: Implement timeline creation.

* Removed ts_api:post_timeline/3, corresponded to `POST` to
  `/ts_api/timelines/<user-id>`, which makes no sense as there is no way to
  auto-generate new timeline-ids.
* Changed ts_api:put_timeline/3 to use ts_timeline:write. This resolved #0017.
* Changed ts_api:put_entry/4 to use ts_entry:write.
* Implemented ts_entry:write/1 and ts_timeline:write/1. These functions are
  simple passthroughs to mnesia:dirty_write/1.
* Added clarifing documentation for ts_json:record_to_ejson JSON formats.
* Renamed ts_json:ejson_to_record/2 to ts_json:ejson_to_record_strict/2 and
  added ts_json:ejson_to_record/2 as a lax version of the same.
* Updated all ts_api functions to use updated ts_json methods.
2011-06-07 20:47:23 -05:00
Jonathan Bernard
bc364b2ebd Merge branch 'client-redesign'
Conflicts:
	yaws.dev.conf
2011-06-07 08:38:09 -05:00
Jonathan Bernard
89336e844f Implemented new timeline functionality client-side.
* Fixed returned information in ts_api:put_timeline/3
* Added UI for new timeline button.
* Added client-side implementation of new timeline creation. There is a problem
  on the server-side, PUT should support creating new items and updating
  existing items.
2011-06-07 08:34:20 -05:00
Jonathan Bernard
76083b5972 Added icons for timeline creation. 2011-06-07 08:29:26 -05:00
Jonathan Bernard
cf42d133f6 Added UI for timeline creation. 2011-06-01 06:29:48 -05:00
Jonathan Bernard
72ef1ac277 Moved issues to issue tracker. 2011-05-27 09:50:43 -05:00
Jonathan Bernard
2cc17b85f1 Implemented notes UI.
- Switched from a global reset in www/css/ts-screen.scss to a selected
  top-level elements reset to allow default formatting for user notes.
- Restructured the #entry-list and entry displays.
- Restructured notes div, now has a sub-div for text and a textarea
  element for input.
- Added Showdown.js, a JavaScript Markdown library for formatting comments.
- Moved EntryView blur events to the View events map.
- Added images for expansion of notes.
- Added the ability to edit notes.
- Split EntryListView.addOne into renderOne and addOne so that renderOne
  can be called with a new entry (fixes duration glitch)
2011-05-16 04:09:37 -05:00
Jonathan Bernard
65a9a517f9 Updated test data. 2011-05-09 10:54:24 -05:00
Jonathan Bernard
808492154d Adding hover-able icons to timeline entries.
- Added notes and delete icons right and left entry marks respectively.
- Notes icon slide toggles the notes display (hidden by default).
2011-05-09 10:52:21 -05:00
Jonathan Bernard
74d8a7f015 Duration display, time formatting, UI tweaks.
Client Behaviour (ts.js)
========================

- Created EntryView.getViewModel: translates model data to view data,
  specifically synthesizes the start time and duration from the timestamp.
- Added nextModel option to EntryView, needed for calculating the entry
  duration.
- Created EntryView.formatStart: given the timestamp, return the start time,
  in HH:MM format. Code is written for both 24hr and 12hr format, still need
  to write a selector mechanism. For now, uses 12hr format.
- Created EntryView.formatDuration: Get the duration of the entry based on
  this entry's timestamp and and the next entry's timestamp in a display-able
  form. If nextModel is `null` or `undefined` it is assumed that `model`
  is the most recent model and duration is calculated against the current time.
- Changed EntryView.render to use getViewModel.
- Added 'blur' listeners to the mark and timestamp input fields to close them
  without persisting the changes.
- Created EntryView.update: Refresh the display based on the model using the
  existing DOM elements.
- EntryView.save() now uses EntryView.update() instead of EntryView.render()
  and no longer includes an implicit close()
- EntryView.close() has been split into seperate save() and close() functions,
  to persist the changes and hide the input dialogs, respectively.
- EntryListView.addOne now passes the nextModel to EntryViews is creates.
- EntryListView.createNewEntryOnEnter() now clear the new intry input after
  creating a new entry.
- EntryListView.render() now uses a for-structure to traverse the entry
  collection and passes the nextModel (if there is one) to EntryListView.addOne.

Client UI (ts-screen.scss)
==========================

- Font size, family, and color adjusted on timeline and user input fields.
- Day seperator secondary header colors adjusted.
- Mark column width shortened, timestamp and duration columns widened.
- Styles added for notes UI

Client UI (index.yaws)
======================

- Markup changes needed for getViewModel chanes.
- Expanded day seperator.
2011-05-07 22:03:02 -05:00
Jonathan Bernard
036177cfed Duration display, time formatting, UI tweaks.
Client Behaviour (ts.js)
========================

- Created EntryView.getViewModel: translates model data to view data,
  specifically synthesizes the start time and duration from the timestamp.
- Added nextModel option to EntryView, needed for calculating the entry
  duration.
- Created EntryView.formatStart: given the timestamp, return the start time,
  in HH:MM format. Code is written for both 24hr and 12hr format, still need
  to write a selector mechanism. For now, uses 12hr format.
- Created EntryView.formatDuration: Get the duration of the entry based on
  this entry's timestamp and and the next entry's timestamp in a display-able
  form. If nextModel is `null` or `undefined` it is assumed that `model`
  is the most recent model and duration is calculated against the current time.
- Changed EntryView.render to use getViewModel.
- Added 'blur' listeners to the mark and timestamp input fields to close them
  without persisting the changes.
- Created EntryView.update: Refresh the display based on the model using the
  existing DOM elements.
- EntryView.save() now uses EntryView.update() instead of EntryView.render()
  and no longer includes an implicit close()
- EntryView.close() has been split into seperate save() and close() functions,
  to persist the changes and hide the input dialogs, respectively.
- EntryListView.addOne now passes the nextModel to EntryViews is creates.
- EntryListView.createNewEntryOnEnter() now clear the new intry input after
  creating a new entry.
- EntryListView.render() now uses a for-structure to traverse the entry
  collection and passes the nextModel (if there is one) to EntryListView.addOne.

Client UI (ts-screen.scss)
==========================

- Font size, family, and color adjusted on timeline and user input fields.
- Day seperator secondary header colors adjusted.
- Mark column width shortened, timestamp and duration columns widened.
- Styles added for notes UI

Client UI (index.yaws)
======================

- Markup changes needed for getViewModel chanes.
- Expanded day seperator.
2011-05-07 21:31:30 -05:00
Jonathan Bernard
b22c10c6c2 Created todo documentation. 2011-05-07 21:30:32 -05:00
Jonathan Bernard
850edf9e25 Updated test data. 2011-05-07 21:30:00 -05:00
Jonathan Bernard
1161f6b752 Added feature brainstorming doc. 2011-05-06 17:47:16 -05:00
Jonathan Bernard
65dbab9c13 Replaced jQuery UI login dialog with home-grown login.
- New login hides the entire underlying page when open.
- Fits the current design.
2011-05-06 17:45:56 -05:00
Jonathan Bernard
06556020d4 UI Tweak: column headers, re-alignments.
- Added 'start' and 'duration' headers to day seperators.
- Right-aligned start and duration columns.
- Changed duration to 'Xhr Ym' instead of 'HH:MM:SS'.
- Added a section using 12hr time format instead of 24hr for comparison.
2011-05-06 13:14:57 -05:00
Jonathan Bernard
dd3387a0f1 Trying to tighten up the design. More functionality implemented. 2011-05-03 12:50:03 -05:00
Jonathan Bernard
cf5153c90b Merge branch 'api-redesign' into client-redesign 2011-05-03 12:40:06 -05:00
Jonathan Bernard
ac80d57cd3 Updated dev conf to reflect the new location of the Timestamper project. 2011-05-03 12:37:15 -05:00
Jonathan Bernard
3d89477f7e Added ext_data field to ts_user. 2011-05-03 12:27:39 -05:00
Jonathan Bernard
ebd4b117c9 Updated database to use lists instead of atoms for table keys. 2011-05-03 12:27:13 -05:00
Jonathan Bernard
9b3f587974 Changed record keys to use lists instead of atoms.
Using atoms will not scale in the large. Also, using atoms as keys forced the
API to convert arbitrary end-user input to atoms, adding another potential
drain of the finite atom-space available.

- ts_api: url paths are now treated and matched as lists, not atoms.
  Usernames and API calls all use lists now.
- ts_json: key items (username, timeline ids) are now expected to be lists,
  not atoms.
2011-05-03 12:24:23 -05:00
Jonathan Bernard
77fc3f2a07 Created prototype HTML. 2011-05-03 12:00:27 -05:00
Jonathan Bernard
f4fe5559b1 Reorganizing UI, adding TimelineListView. 2011-04-27 16:59:33 -05:00
Jonathan Bernard
302bc9ccdd Trying to gather thoughts and code coherently.
Added client-side architecture model.
Playing with UI code.
2011-04-27 14:11:54 -05:00
Jonathan Bernard
9025a2f8f6 Trying a view for user, timeline(s), entry(ies). 2011-04-20 09:40:54 -05:00
Jonathan Bernard
aec172536e Merge branch 'api-redesign' into client-redesign 2011-04-15 14:30:28 -05:00
Jonathan Bernard
5b267ec67b Switched usage of POST and PUT verbs.
- POST and PUT were being used counter to typical usage, POST updating existing
  records and PUT creating new ones. Changed to PUT updating existing records
  and POST creating records.
2011-04-15 13:53:24 -05:00
Jonathan Bernard
17c5b9cbd1 API now returns just the content in the body.
- Instead of returning Meta-Data (status) in the header and the body, we now
  only return the content in the body, except in error when we return the error
  message.

- JSON entries use the 'id' name for entity ids (instead of 'username',
  'timeline_id', and 'entry_id')
2011-04-15 13:51:01 -05:00
Jonathan Bernard
e10ec26a21 Implementing new client design using Backbone.js. 2011-04-15 13:26:55 -05:00
Jonathan Bernard
d0aab2d0c6 merge. 2011-04-10 17:20:43 -05:00
Jonathan Bernard
b6d05665bf Moved timestamper to root of server. 2011-04-10 17:16:30 -05:00
Jonathan Bernard
81fb56022c Merge branch 'stable' 2011-03-17 04:17:26 +00:00
Jonathan Bernard
76f32ac834 Moved timestamper under JDB Labs. 2011-03-17 04:17:10 +00:00
Jonathan Bernard
4d124ffeba Fixing dev config. Starting to implement sign-up.
Added 'default' make target to only compile.
Reset development configuration.
Commented out starts of tests.
Added sign-up GUI to login panel.
Moved application from /ts/ to /
2011-03-16 07:39:09 -05:00
Jonathan Bernard
1b981b206c Created timestamper_dev module, logging. 2011-03-11 15:08:18 +00:00
Jonathan Bernard
b59b628cb5 Deployed to dev01 on the Rrackspace cloud. 2011-03-11 12:32:06 +00:00
Jonathan Bernard
3c3d553768 Bugfix in ts_api:delete_entry/1, bad case clause ordering. 2011-03-10 16:00:46 -06:00
Jonathan Bernard
39c3b83d3f Implemented edit and update for entries.
- Added ts_entry:delete/1 to delete an entry from the database.
- Implemented ts_api:delete_entry/3.
- Added a form to facilitate editing individual entries.
- Moved the small show/hide functions directly into the HTML.
- Wired up the update timeline form.
- Wired up the edit and update entry form.
2011-03-08 18:02:33 -06:00
Jonathan Bernard
1b1e31059b Implemented entry creation, pagination. Some restyling and bugfixes.
- Bug fix in ts_api:list_entries/3. Case statement matching on atoms but
  input is a list (string).
- Bug fix in ts_api:put_entry/3. Was expecting the wrong result from
  ts_entry:new/1.
- Bug fix in ts_entry:list/4. Code crashed when the starting offset was
  greater than the total number of elements. Now returns [].
- Fixed ts_json:encode_datetime/1 and ts_json:decode_datetime/1 to handle
  millisecond values in the datetime string (per ISO standard).
- Broke out ``control-links`` style to a top-level class.
- Added showdown.js, a JS Markdown processor. Not hooked up to anything
  yet but intend to display entry notes with Markdown.
- Added code for entry pagination. Loads the most recent 20 entries and
  loads more upon demand in batches of 20.
- Fixed bug in login routine that kept the user edit fields from being
  pre-populated.
- Rewrote the loadEntries function to double for new entries and loading
  more existing entries.
- Commented displayEntries. Also refactored into displayNewerEntries,
  which pushed new entries on to the top of the stack, and
  displayOlderEntries, which tags them onto the bottom.
- Implemented hidden notes field for new entry input.
- Implemented new entry creation.
- Created a helper function to ISO format a Date object.
- Expanded entry template to show control links (edit, show notes, del).
- Activated the 'load more entries' button.
2011-03-07 16:43:40 -06:00
Jonathan Bernard
c185c8cd81 Deciding on UI for entry list. 2011-03-06 13:46:18 -06:00
Jonathan Bernard
e6fcf736bb Reset configs again, forgot. 2011-03-03 17:11:27 -06:00
Jonathan Bernard
122a3bd1e3 Started implementing entry loading in client side.
- Bug fix in ts_entry:new/1. Msspelled ``atomic``.
- Bug fix in ts_json:record_to_ejson/1. For ``ts_entry`` records, the
  Username and TimelineId elements were not being converted from atoms to list.
- Added the entry template for loaded and created entry elements.
- Added ICanHaz.js (which wraps mustache.js) and underscore.js.
- Implemented a naive version of displayEntries() in ts.js.
- Added debug alerts for error cases in ts.js.
- Styling the new entry elements.
2011-03-03 17:09:13 -06:00
Jonathan Bernard
dfab257a12 Reset the config files. 2011-03-01 18:13:47 -06:00
Jonathan Bernard
348c73a36f Bugfix and documentation.
- Fixed a bug in ts_api:list_timelines/2 and ts_api:list_entries/3, which
  respond only to GET requests but were looking for POST data.
- Added documentation for ts.js.
2011-03-01 18:00:51 -06:00
Jonathan Bernard
efab46f167 Win32-specific configuration. 2011-03-01 08:59:10 -06:00
Jonathan Bernard
439a080f14 Started adding implementation of client-side functionality.
- Changed ts_api:dispatch_user/3 to return information for the user currently,
  authenticated if a valid session id is presented and no username is presented.
- Moved the generic styling of form > * elements to be specific to .bar > form.
- Added jQuery U 1.8.0.
- Created login dialog that will automatically load upon page load if the user
  is not logged in.
- Added styling for jQuery UI login dialog.
- Implemented login functionality on the client page.
- Implemented functionality to load user and timeline records on the client
  side.
2011-03-01 08:23:51 -06:00
Jonathan Bernard
4492f87a39 Added user_summary to api.
Added ts_api:dispatch_app/3
Added ts_api:get_user_summary/2
Fixed compile errors in ts_user.
Added comments.
2011-02-24 07:29:30 -06:00
Jonathan Bernard
1a3c0d5c4e Further updates to page. Small update to internal API.
Internal API update functions now accept partial updates, allowing one to
selectively update only some fields of a particular record.
2011-02-14 17:16:37 -06:00
Jonathan Bernard
ddc12cec64 Further work on web interface. 2011-02-14 09:01:32 -06:00
Jonathan Bernard
19fc37c772 Working on new entry. 2011-02-13 11:38:50 -06:00
Jonathan Bernard
d743635865 Added more JS event handler implementations. 2011-02-12 14:57:29 -06:00
Jonathan Bernard
6197363508 More progress on the application page.
Created and styled the forms for updating user/timeline information.
Wired the hidden forms to links that expose them using jQuery.
2011-02-12 07:48:19 -06:00
Jonathan Bernard
9c645e9a88 Changed the header and link presentation. 2011-02-11 17:09:58 -06:00
Jonathan Bernard
6bdf0ab526 The main app page is starting to take form.
Created stylesheet for page.
Added real content and structure to the app page.
2011-02-11 16:35:11 -06:00
Jonathan Bernard
dcd836ba8e Testing out design ideas. 2011-02-11 08:32:12 -06:00
Jonathan Bernard
afca12ecc9 Started working on the client-side code. 2011-02-10 07:47:35 -06:00
Jonathan Bernard
0642c18a6e Implemented cookie-based authentication to the API.
Created timestamper module to start the application.
Added cookie-based authentication to ts_api.
Added utility methods to ts_api:
    * make_json_400/1 and make_json_400/1
    * make_json_401/1 and make_json_401/2
    * parse_json_body/1 reads a JSON object from a HTTP request body.
Implemented ts_api_session module to manage api user sessions.
Fixed ts_entry:list* methods to be 0-indexed.
Removed the ts_json:ejson_to_record/1 implementation for ts_user records.
    Decided that ts_user records are never trusted from the client,
    manipulation of fields such as pwd, username will be restricted to
    app pages.
Changed the password hashing algorithm. Now uses SHA1(pwd + 256bit salt).
    Want to use bcrypt, investingating cross-platform bcrypt implementation.
Fixed yaws.conf config file.
2011-02-07 08:56:07 -06:00
Jonathan Bernard
5809ed3959 Implementing API 2011-02-05 08:57:34 -06:00
Jonathan Bernard
6e2e0d5f00 Redesigned API URL structure. Updated ts_api to implement this.
Implemented ts_api:list_timelines/2.
Adjusted ts_timeline:list/3 to be 0-indexed.
Changed ts_user password hash to use a random salt + SHA1
Added some skeleton testing code.
2011-02-04 17:19:53 -06:00
Jonathan Bernard
1575e25898 Implemented ts_api:get_user/2, ts_api:put_user/1, and ts_api:post_user/1.
Created a utility function for OK results, ts_api:make_json_200/2
Created ts_common:new/1 and ts_common:update/1 to generalize record creation and update.
Refactored ts_timeline:new/1 and ts_timeline:update/1 to use the ts_common functions.
Implemented ts_json:record_to_ejson/1 and ts_json:ejson_to_record/1 for ts_user records.
Implemented ts_user module.
2011-02-03 17:23:40 -06:00
Jonathan Bernard
6fe9184c8e Continuing work on the API.
The id_counter module now includes the record directly in the source.
Fixed many typos and small syntax errors.
Added ts_api:dispatch_user/2 to handle different HTTP methods.
Added method placeholder stubs to ts_api.
Implemented ts_api:list_entries/3.
Added ts_user record and ts_user module.
Implemented ts_entry:list/4, the more generic guts of the other list functions.
Created ts_entry:list_asc/3 and ts_entry:list_desc/3.
Fixed ts_json:encode_datetime/1.
2011-02-02 16:57:58 -06:00
Jonathan Bernard
098cd4cbb9 Continue to implement the API.
Started implementing ts_api:list_entries/3
Implemented ts_api:get_entry_by_id/4
2011-01-31 10:04:01 -06:00
Jonathan Bernard
309d6915fc Additional parts of the API, DB layer.
Implemented additional API functions:
    * dispatch_timeline/4
    * dispatch_event_by_id/4
    * get_timeline/3
    * put_timeline/3
    * post_timeline/3
    * make_json_404/2, make_json_405/2, make_json_500/1
Implemented ts_timeline:lookup/2
Implemented ts_entry:lookup/2
2011-01-30 08:28:56 -06:00
Jonathan Bernard
b690326cf4 Small addition to ts_api. 2011-01-29 13:41:27 -06:00
Jonathan Bernard
0d96c26174 Started craeting API dispatch and implementation. Updated api doc. 2011-01-29 10:46:45 -06:00
Jonathan Bernard
c04b73d9cc More implementation.
Modified intent of ts_db_records. timestamp value is no longer a
  {Date, Time} value, it is now gregorian seconds since year 0.
  This was done to make queries that are based on date comparisons
  easier to write and more efficient.
Added date time comparison function to ts_common.
Implemented update/1 and list/3 for ts_entry.
2011-01-28 16:57:15 -06:00
Jonathan Bernard
495336fc58 Starting DB implementation.
Added Makefile, id_counter.erl, and yaws.conf.
Created ts_common module. Will contain common DB code. So far only list/3.
Created ts_timeline module. DB code for storing timeline entries.
Created ts_entry module, DB code for storing timelin entries.
2011-01-28 06:49:47 -06:00
Jonathan Bernard
111da51c73 Beginning API documentation. 2011-01-28 03:22:21 -06:00
Jonathan Bernard
d08d054fbe Finished version 2.1.
Updated .gitignore, excluding vim temp filesn and build directories.
Promoted version number in application.properties.
Switched to SLF4J for logging from LOG4J.
Switched to jdb-util SmartConfig for configuration from  java.util.Properties
Added plugin architecture:
    * Plugins implement com.jdbernard.timestamper.gui.plugin.TimestamperPlugin
    * Provides five hooks into the application:
        - onStartup: called as the aaplication is starting.
        - onExit: called as the application is exiting.
        - onTimelineLoad: called when the application loads a timeline.
        - onNewTask: called when the user creates a new task.
        - onDeleteTask: called when the user deletes a task.
    * Plugins must be on the classpath to be enabled.
    * A new timestamperrc property allows the user to specify a plugin
      directory. That directory and any JAR files in it will be added to the
      classpath used to load plugins. This property is 'plugin.dir' and may
      contain the path, If no directory exists at that path, one will be
      created. The default path is './plugins'.
    * A new timestamperrc property allows the user to specify which plugins
      to load when the application is started: 'plugin.classes'. It expects
      a comma-seperated list of plugin class names. The default value is
      ''.
    * Two default plugins have been created:
        - 'com.jdbernard.timestamper.gui.plugin.HookLogger'. This plugin logs
          an info message every time one of the plugin hooks is called.
        - 'com.jdbernard.timestamper.gui.plugin.XMPPStatusUpdater'. This
          plugin updates a user's XMPP (Jabber) presence with their current
          task each time a new task is entered.
Various other changes on TimeStamperMainController:
    * Timeline loading is now broken out into a load() closure.
    * Plugin hooks described above added at appropriate places.
    * Added a general wrapPluginCall method to catch any exceptions from a
      plugin call and sanitize the return value.
    * The exitGracefully closure now hides the GUI before starting its shutdown
      sequence so the user and the OS are less likely to assume the app has
      hung.
    * Added the functionality for the 'persistOnUpdate' feature (introduced on
      TimelineProperties, below).
Removed the automatically generated logging functions, traceIfEnabled and
  debugIfEnabled--which were redundant and pointless overhead.
Added check box menu option on TimeStamperMainView for the 'persistOnUpdate'
  feature
Changes on TimelineProperties:
    * Switched to using SmartConfig and renamed several properties:
        - remote.timeline.<name>.push -> remote.timeline.<name>.push?
        - remote.timeline.<name>.pull -> remote.timeline.<name>.pull?
        - remote.timeline.<name>.save-on-exit -> remote.timeline.<name>.syncOnExit?
        - remote.timeline.<name>.update-interval -> remote.timeline.<name>.updateInterval
    * Added a feature, 'persistOnUpdate'. If set to true, this signals the
      application that it should persist the Timeline on each update.
    * Added a property 'timeline.persistOnUpdate?'.
2011-01-22 20:45:08 -06:00
Jonathan Bernard
652cc8703a Changes to Description categories, other fixes.
Description based category is now case-insensitive.
Fixed bug where description categories and plans were not agreeing on how
  to categories items.
Updated startscript.groovy to be aware of new changes with events.
Update build script properties.
2011-01-22 19:31:36 -06:00
Jonathan Bernard
f3a049777a Many changes, bad commit message. 2011-01-18 17:58:29 -06:00
Jonathan Bernard
e4a3b967de Refactored to remove Entry and use Event everywhere. 2011-01-18 07:20:56 -06:00
Jonathan Bernard
ab1c7f2393 Fleshed out CategorizationPlan implementation.
Added findEntriesToRecategorize() to CategorizationPlan
Refactored createNewCategory() to newCategory() on CategorizationPlan
Refactored Category to use CategorizationPlans
Created CatPlan for DescriptionBasedCategory
Made Event cloneable.
Refactored Category implementations to be aware of the single arg
  Category constructor.
Created TwoLevelCategory, for entries which inherently have two levels
  of categorization in them.
Created a CatPlan for TwoLevelCategory
Removed the specialization implementation, ITHelpCategory: the new
  TwoLevelCategory is a more general version of the same.
Created CatPlan for TicketCategory
Fixed TicketCategory and TicketPlan to adjust for small difference between
  the old behavour of ITHelpCategory and new TwoLevelCategory
Updated testing starter script.
2011-01-12 17:21:39 -06:00
Jonathan Bernard
24600b46d9 Put in CategorizationPlan architecture. 2011-01-12 06:54:03 -06:00
Jonathan Bernard
ba04aae34e Fixed, expanded category implementations. Started chart work.
Added FilteredCategory, filters by arbitrary CategoryFilters.
Started on Util class with code to create charts based on categories.
Fixed ITHelp ticket matching to swallow optional space after "ITHelp:"
Fixed TicketCategory to use it's own addEvent logic instead of inheriting the
  default Category implementation.
Updated startscript.groovy to reflect test data for recent changes.
2011-01-11 08:21:41 -06:00
Jonathan Bernard
6de924927a Created TimelineEventProcessor. Tested existing implementation. 2011-01-10 18:03:19 -06:00
Jonathan Bernard
68b51640af Finised initial implementation. Need to test. 2011-01-10 00:24:08 -06:00
Jonathan Bernard
9f4008a775 Initial commit 2011-01-09 15:11:36 -06:00
jdbernard@localhost
c964730f03 Upgraded to Griffon 0.3 2010-04-11 16:00:50 +02:00
Jonathan Bernard
dbb2121525 Beginning update to add GUI logging. 2010-04-02 05:43:04 -05:00
Jonathan Bernard
5b3e2066a2 Upgraded Griffon to 0.2.1. 2010-04-01 22:29:07 -05:00
Jonathan Bernard
7f2c1a6d53 Added documentation, fleshed out accessors for class Timeline. 2010-01-06 09:57:17 -06:00
Jonathan Bernard
6d212ea771 Finished catching up to 1.7 functionality. 2009-12-26 14:49:46 -06:00
Jonathan Bernard
cee6de1147 Removed Action portions. Added logging framework. GUI architecture changed (move to dependancy injection) 2009-12-23 18:29:13 -06:00
Jonathan Bernard
5b19542355 Added PropertyChangeSupport to TimelineDayDisplay and decoupled it from the GUI
Added log4j lib
2009-12-23 16:14:18 -06:00
Jonathan Bernard
bff340e910 Incremental GUI updates. Copied over code for TimelineDayDisplay. 2009-12-22 19:26:47 -06:00
Jonathan Bernard
02fc6b5bb7 Added NotesDialog MVC. Wired window movement. Added GUIUtil 2009-12-21 14:06:32 -06:00
Jonathan Bernard
5000d266fe Created showNotesAction and ShowPunchcardAction 2009-12-20 20:03:49 -06:00
Jonathan Bernard
f1ab340b11 Iterative development on GUI 2009-12-18 16:15:14 -06:00
jdbernard@jdbernard-inspiron
612234b345 Progress made on GUI skeleton 2009-12-18 00:31:56 -06:00
Jonathan Bernard
e08d6d6c5d Begin restructuring using Griffon 2009-12-17 23:12:09 -06:00
Jonathan Bernard
8232705a60 Cleanup 2009-12-17 07:35:07 -06:00
Jonathan Bernard
89ad2ac447 Restructured the project. Moving to multiple-source timelines
that can automatically sync with one another.
2009-12-17 07:32:28 -06:00
Jonathan Bernard
42d2d82c74 This version of the properties file uses it's own format. 2009-10-15 23:12:55 -05:00
Jonathan Bernard
59a81a4f77 Finished pretty_format script 2009-09-22 17:47:46 -05:00
Jonathan Bernard
04c4a2b6ac Began pretty-print script. 2009-06-23 18:43:38 -05:00
Jonathan Bernard
1ff581d4a5 Converted repo to mercurial.
Moved .gitignore to .hgignore
2009-02-05 22:51:40 -06:00
convert-repo
9148b8f6f6 update tags 2009-02-06 10:50:46 +00:00
Jonathan Bernard
e69faa148f Version 1.7 - Added 30-minute auto save
committer: Jonathan Bernard <jdbernard@gmail.com>
2008-11-11 19:29:58 -06:00
Jonathan Bernard
efcb781ae4 Added .gitignore file
committer: Jonathan Bernard <jdbernard@gmail.com>
2008-10-19 20:03:05 -05:00
Jonathan Bernard
6d451c42fd Small changes to generalize build environment.
The jcalendar lib path in nbproject/project.properties was pointing to
  the absolute path rather than a relative one.

committer: Jonathan Bernard <jdbernard@gmail.com>
2008-10-19 20:01:33 -05:00
Jonathan Bernard
8d74a5a259 Added about dialog, bug fixes
Fixed edge cases in TimelineDayDisplay (first/last/no markers)
Fixed bug in PunchcardDisplayDialog - timeline not changed when loaded.

committer: Jonathan Bernard <jdbernard@gmail.com>
2008-10-19 19:00:34 -05:00
Jonathan Bernard
ffdce9c00d Finished version 1.5
Added timeline viewer for a single day
  Added icon resources
  Modified build file to clean more stuff

committer: Jonathan Bernard <jdbernard@gmail.com>
2008-10-18 18:18:51 -05:00
Jonathan Bernard
789e708f69 Began work on PunchcardDisplayDialog to display timestamps on various timelines.
Modified build file to clean additional files (logs, etc)

committer: Jonathan Bernard <jdbernard@gmail.com>
2008-10-17 19:49:29 -05:00
Jonathan Bernard
0914ea5b6c Tried JNLP, not workingas expected, so diabled. Artifacts remain.
Bug fixes

committer: Jonathan Bernard <jdbernard@gmail.com>
2008-09-11 18:15:57 -05:00
Jonathan Bernard
2b850b9586 Small changes to UI
committer: Jonathan Bernard <jdbernard@jdbernard-desktop.(none)>
2008-09-09 15:03:19 -05:00
Jonathan Bernard
5798774181 Final changes before 1.3 and versioning work.
Notes text area wraps words.

committer: Jonathan Bernard <jdbernard@gmail.com>
2008-09-03 22:18:44 -05:00
Jonathan Bernard
b47ab393c6 Fixed size issue and application icon
committer: Jonathan Bernard <jdbernard@gmail.com>
2008-09-03 21:46:35 -05:00
Jonathan Bernard
ceebe27075 Updated version information. Made all version changes.
committer: Jonathan Bernard <jdbernard@gmail.com>
2008-09-03 20:32:34 -05:00
Jonathan Bernard
83c8933a0a Added notes dialog.
Refined snap behaviour to be more generic.
  Snap functionality broken out as a seperate method.
  NotesDialog uses snap method to snap to app frame and screen

committer: Jonathan Bernard <jdbernard@gmail.com>
2008-09-03 20:29:03 -05:00
Jonathan Bernard
fb83bc9161 Bug fix: close log handlers on exit.
committer: Jonathan Bernard <jdbernard@jdbernard-desktop.(none)>
2008-08-31 00:05:59 -05:00
Jonathan Bernard
5a992da480 Added ability to specify timeline file on command line
Added release build target to build stand-alone jar and tar.gz files

committer: Jonathan Bernard <jdbernard@jdbernard-desktop.(none)>
2008-08-30 23:35:42 -05:00
Jonathan Bernard
34e52d78bf Referencing libraries in an IDE-independant way using relative paths to the
'lib' directory so that build scripts can run on any platform and outside
  the IDE

committer: Jonathan Bernard <jdbernard@jdbernard-desktop.(none)>
2008-08-30 00:29:22 -05:00
Jonathan Bernard
0864b978e7 missed some IDE stuff.
committer: Jonathan Bernard <jdbernard@gmail.com>
2008-08-29 18:30:58 -05:00
Jonathan Bernard
ed5bdce87c Minor bug fix. Now allows a config file to be created if it does not exist.
committer: Jonathan Bernard <jdbernard@gmail.com>
2008-08-29 18:27:48 -05:00
Jonathan Bernard
cb419c658f Initial working version checkin.
committer: Jonathan Bernard <jdbernard@gmail.com>
2008-08-29 18:19:56 -05:00
Jonathan Bernard
373f813c7c Added initial GUI code from NetBeans IDE
committer: Jonathan Bernard <jdbernard@gmail.com>
2008-08-29 14:51:55 -05:00
Jonathan Bernard
5582c44608 Created and finished initial version of timeline implementation.
committer: Jonathan Bernard <jdbernard@jdbernard-desktop.(none)>
2008-08-29 12:31:39 -05:00
338 changed files with 24080 additions and 26 deletions

9
.gitignore vendored
View File

@ -1,2 +1,9 @@
*.sw?
build/
dist/
.gradle/
staging/
temp/
release
.sass-cache
*.build.tar.gz
*.sw?

View File

@ -1,13 +0,0 @@
<project name="JDB Labs TimeStamper Library for Java">
<import file="jdb-build-1.10.xml"/>
<property environment="env"/>
<property file="project.properties"/>
<target name="release" depends="build">
<mkdir dir="${basedir}/release"/>
<copy file="${build.dir}/${name}-${version}.${build.number}.jar"
tofile="${basedir}/release/${name}-${version}.jar"/>
</target>
</project>

20
cli/build.gradle Normal file
View File

@ -0,0 +1,20 @@
apply plugin: "groovy"
apply plugin: "maven"
group = "com.jdblabs.timestamper"
version = "1.2"
repositories {
mavenLocal()
mavenCentral() }
dependencies {
compile 'ch.qos.logback:logback-classic:1.1.2'
compile 'ch.qos.logback:logback-core:1.1.2'
compile 'com.martiansoftware:nailgun-server:0.9.1'
compile 'org.slf4j:slf4j-api:1.7.10'
compile 'com.jdbernard:jdb-util:3.4'
compile 'com.jdblabs.timestamper:timestamper-lib:2.1'
compile files('lib/jansi-1.12-SNAPSHOT.jar')
}

24
cli/build.xml Normal file
View File

@ -0,0 +1,24 @@
<project name="JDB Labs TimeStamper CLI" default="build">
<property environment="env"/>
<property file="project.properties"/>
<import file="jdb-build-1.10.xml"/>
<target name="package" depends="build">
<property name="package.dir" value="${build.dir}/${name}-${version}"/>
<mkdir dir="${package.dir}/lib"/>
<copy file="${build.dir}/${name}-${version}.${build.number}.jar"
tofile="${package.dir}/${name}-${version}.jar"/>
<copy todir="${package.dir}">
<filterset><filter token="VERSION" value="${version}"/></filterset>
<fileset dir="${resources.dir}/bin"/>
</copy>
<copy todir="${package.dir}/lib">
<fileset dir="${build.dir}/lib/runtime/jar"/>
<fileset dir="${resources.dir}/config"/>
</copy>
<zip basedir="${build.dir}" includes="${name}-${version}/"
destfile="${build.dir}/${name}-${version}.zip"/>
</target>
</project>

Binary file not shown.

4
cli/resources/bin/ts Executable file
View File

@ -0,0 +1,4 @@
curdir="`pwd`"
cd ~/programs/timestamper-cli-@VERSION@
java -cp "lib:lib/*:./*" com.jdblabs.timestamper.cli.TimeStamperCLI -d "$curdir" "$@"
cd "$curdir"

View File

1
cli/settings.gradle Normal file
View File

@ -0,0 +1 @@
rootProject.name = "timestamper-cli"

View File

@ -0,0 +1,320 @@
package com.jdblabs.timestamper.cli
import com.jdbernard.io.NonBlockingInputStreamReader
import com.jdbernard.util.SmartConfig
import com.jdbernard.util.LightOptionParser
import com.jdblabs.timestamper.core.Timeline
import com.jdblabs.timestamper.core.TimelineMarker
import com.jdblabs.timestamper.core.TimelineProperties
import com.martiansoftware.nailgun.NGContext
import org.fusesource.jansi.AnsiConsole
import static org.fusesource.jansi.Ansi.*
import static org.fusesource.jansi.Ansi.*
import static org.fusesource.jansi.Ansi.Color.*
public class TimeStamperCLI {
private static String EOL = System.getProperty("line.separator")
private static TimeStamperCLI nailgunInst
protected TimelineProperties timelineProperties
protected Timeline timeline
public static final String VERSION = "1.2"
protected static def cli = [
'h': [longName: 'help'],
'v': [longName: 'version'],
'd': [longName: 'working-directory', arguments: 1],
't': [longName: 'timeline-config', arguments: 1],
'tty': [longName: 'tty', arguments: 1]]
public static void main(String[] args) {
TimeStamperCLI inst = new TimeStamperCLI()
doMain(inst, args, System.in, System.out, System.err); }
public static void nailMain(NGContext context) {
if (nailgunInst == null)
nailgunInst = new TimeStamperCLI()
doMain(nailgunInst, context.args, context.in, context.out, context.err) }
protected static void doMain(TimeStamperCLI inst, String[] args,
def sin, def out, def err) {
//out = new PrintStream(AnsiConsole.wrapOutputStream(out))
def opts = LightOptionParser.parseOptions(cli, args as List)
File workingDir = new File(opts.d[0] ?: '.')
String ttyDevice = opts.tty ?: '/dev/tty'
if (opts.h) println USAGE
if (opts.v) {
println "TimeStamperCLI v${VERSION}"
println "By JDB Labs (https://www.jdb-labs.com)"
return }
if (opts.t) {
File propFile = new File(opts.t)
if (!propFile.isAbsolute()) propFile = new File(workingDir, opts.t)
inst.showTimeline(propFile, sin, out, err, ttyDevice) }
else if (inst.timeline == null) {
// Look for .timestamperrc user config file
File cfgFile = new File(
System.getProperty('user.home'), ".timestamperrc")
if (!cfgFile.exists() || !cfgFile.isFile())
err.println "Could not find the user configuration file: " +
cfgFile.canonicalPath
else {
def cfg = new SmartConfig(cfgFile)
inst.showTimeline(new File(cfg.lastUsed), sin, out, err, ttyDevice) } }
else { inst.showTimeline(sin, out, err, ttyDevice) } }
public TimeStamperCLI() { }
public void showTimeline(File timelinePropertiesFile,
def sin, def out, def err, String ttyDevice) {
if (!timelinePropertiesFile.exists() ||
!timelinePropertiesFile.isFile()) {
err.println "No such timeline property file: " +
timelinePropertiesFile.canonicalPath
return }
this.timelineProperties = new TimelineProperties(timelinePropertiesFile)
this.timeline = timelineProperties.timeline
showTimeline(sin, out, err, ttyDevice) }
public void showTimeline(final def sin, def out, def err, String ttyDevice) {
//out.println ""
def currentMarker = timeline.getLastMarker(new Date())
def reader = new NonBlockingInputStreamReader(sin)
Thread readerThread = new Thread(reader)
readerThread.start()
boolean running = true
def blockingReadLine = {
String line = null;
while (line == null && readerThread.isAlive()) {
line = reader.readLine()
Thread.sleep(200) }
return line }
def readNotes = {
out.println(ansi().fg(YELLOW).
a("Notes (end with EOF or a blank line):").reset())
String notes = ""
String line = null
line = blockingReadLine()
while(line != "" && line != "EOF" && line != null) {
notes += line + EOL
line = blockingReadLine() }
return notes }
def printPrompt = {
out.print(formatMarker(currentMarker) + EOL +
ansi().fg(YELLOW).a("> ").reset())
out.flush() }
String line = null
printPrompt()
while (running && readerThread.isAlive()) {
// Handle user input
line = reader.readLine()
if (line != null) {
out.flush();
switch (line) {
case ~/quit|exit|\u0004/:
running = false;
break
case ~/n|new/:
// Read mark
out.println(ansi().fg(YELLOW).a("New timestamp mark:").reset())
String mark = blockingReadLine()
// Read notes
String notes = readNotes();
// Create marker
currentMarker = new TimelineMarker(new Date(), mark, notes)
timeline.addMarker(currentMarker)
if (timelineProperties.persistOnUpdate)
timelineProperties.save()
break
case ~/h|help/:
out.println(ansi().fg(RED).
a("Not yet implemented.").reset());
break
case ~/l|list|history/:
out.println(ansi().fg(RED).
a("Not yet implemented.").reset());
break
case ~/s|save/:
timelineProperties.save()
break
case ~/reload/:
timeline = timelineProperties.reloadTimeline()
currentMarker = timeline.getLastMarker(new Date())
break
case ~/e|ed|edit/:
reader.pause()
out.println(ansi().fg(YELLOW).
a("Press ENTER to launch an editor for the current marker..."))
out.flush()
blockingReadLine()
currentMarker = edit(currentMarker, ttyDevice)
reader.resume()
break
case ~/d|del|delete/:
timeline.removeMarker(currentMarker)
currentMarker = timeline.getLastMarker(new Date())
break
default:
String notes = readNotes()
currentMarker = new TimelineMarker(new Date(), line, notes)
timeline.addMarker(currentMarker)
if (timelineProperties.persistOnUpdate)
timelineProperties.save()
break
}
printPrompt()
} else {
out.print(ansi().saveCursorPosition().cursorUpLine().eraseLine().toString() +
formatMarker(currentMarker) +
ansi().cursorDown(1).restorCursorPosition().toString())
out.flush();
Thread.sleep(200)
}
}
this.timelineProperties.syncTargets.each { it.shutdown() }
if (readerThread.isAlive()) {
readerThread.interrupt();
readerThread.join(500);
if (readerThread.isAlive()) readerThread.stop(); }
out.println ""
}
protected TimelineMarker edit(TimelineMarker tm, String ttyDevice) {
File temp = File.createTempFile('timestamp-mark-', '.txt')
temp.withPrintWriter { fout ->
fout.println("""\
# Edit the time, mark, and notes below. Any lines starting with '#' will be
# ignored. When done, save the file and close the editor.""")
fout.println(Timeline.longFormat.format(tm.timestamp))
fout.println(tm.mark)
fout.println("""\
# Everything from the line below to the end of the file will be considered
# notes for this timeline mark.""")
fout.println(tm.notes) }
['sh', '-c', "\$EDITOR ${temp.canonicalPath} <${ttyDevice} >${ttyDevice}"].execute().waitFor()
Thread.sleep(200)
String newTimeStr
String newMark
String newNotes = ""
temp.eachLine { line ->
if (line =~ /^\s*#/) return
if (!newTimeStr) newTimeStr = line
else if (!newMark) newMark = line
else newNotes += line + EOL }
Date newTime
try { newTime = Timeline.longFormat.parse(newTimeStr) }
catch(Exception e) {
out.println(ansi().fg(RED).a("Invalid timestamp format. The " +
"previous timestamp value will be retained."))
newTime = currentMark.timestamp }
timeline.removeMarker(tm)
def newMarker = new TimelineMarker(newTime, newMark, newNotes)
timeline.addMarker(newMarker)
if (timelineProperties.persistOnUpdate)
timelineProperties.save()
return newMarker
}
protected static String formatMarker(TimelineMarker tm) {
return ansi().fgBright(CYAN).
a(Timeline.shortFormat.format(tm.timestamp) + " ").fg(GREEN).
a("(" + getDuration(tm) + ") ").fg(WHITE).a(tm.mark).toString() }
protected static String getDuration(TimelineMarker tm) {
Date currentTime = new Date()
long seconds = currentTime.time - tm.timestamp.time
seconds /= 1000
long minutes = seconds / 60
seconds = seconds % 60
long hours = minutes / 60
minutes %= 60
long days = hours / 24
hours %= 24
StringBuilder sb = new StringBuilder()
if (days > 0) sb.append(days + "day ")
if (hours > 0) sb.append(hours + "hr ")
if (minutes > 0) sb.append(minutes + "min ")
sb.append(seconds + "sec")
return sb.toString() }
public static final String USAGE = """\
TimeStamperCLI v${VERSION}
By JDB Labs (https://www.jdb-labs.com)
Usage:
ts <OPTIONS>
where OPTIONS is one or more of:
-h, --help Print this usage information.
-v, --version Print version information.
-d, --working-directory Set the application's working direcotry (defaults to
the current directory of the executing process).
-t, --timeline-config Set the timeline configuration file to use to access
the timeline. By default, the value of the
`lastUsed` property in the \$HOME/.timestamperrc
file is used to find the timeline configuration file
to use.
--tty Manually set the name of the TTY device to use. This
defaults to `/dev/tty`.
"""
}

62
gui/.classpath Normal file
View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main"/>
<classpathentry kind="src" path="griffon-app/conf"/>
<classpathentry kind="src" path="griffon-app/models"/>
<classpathentry kind="src" path="griffon-app/views"/>
<classpathentry kind="src" path="griffon-app/controllers"/>
<classpathentry kind="src" path="griffon-app/resources"/>
<classpathentry kind="src" path="test/integration"/>
<classpathentry kind="src" path="test/unit"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="GRIFFON_HOME/ant/lib/ant.jar"/>
<classpathentry kind="var" path="GRIFFON_HOME/lib/swingx-0.9.3.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/swing-worker.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/commons-lang-2.4.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/ant-launcher-1.7.1.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/gant_groovy1.6-1.6.0.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/asm-2.2.3.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/commons-cli-1.0.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/groovy-all-1.6.4.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/swingxbuilder-0.1.6-SNAPSHOT.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/jline-0.9.94.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/svnkit-1.2.0.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/log4j-1.2.15.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/ant-nodeps-1.7.1.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/ant-1.7.1.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/ant-trax-1.7.1.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/commons-logging-1.1.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/ant-junit-1.7.1.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/MultipleGradientPaint.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/spring-2.5.6.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/lib/junit-3.8.2.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/dist/griffon-rt-0.2.1.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/dist/griffon-resources-0.2.1.jar" />
<classpathentry kind="var" path="GRIFFON_HOME/dist/griffon-cli-0.2.1.jar" />
<classpathentry kind="output" path="staging/classes"/>
</classpath>

4
gui/.hgignore Executable file
View File

@ -0,0 +1,4 @@
.*.swp
.*.swo
staging
dist

18
gui/.project Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>TimeStamper</name>
<comment/>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

BIN
gui/CHANGE ME Normal file

Binary file not shown.

43
gui/TimeStamper.iml Normal file
View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<module relativePaths="true" type="GRIFFON_MODULE" version="4">
<component name="FacetManager">
<facet type="Griffon" name="Griffon">
<configuration />
</facet>
<!--
<facet type="Spring" name="Spring">
<configuration>
<fileset id="Griffon" name="Griffon" removed="false">
<file>file://$MODULE_DIR$/web-app/WEB-INF/applicationContext.xml</file>
</fileset>
</configuration>
</facet>
-->
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/griffon-app/conf" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/griffon-app/models" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/griffon-app/views" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/griffon-app/controllers" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/griffon-app/lifecycle" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test/integration" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/test/unit" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Griffon 0.3" level="project" />
<orderEntry type="module-library">
<library name="Griffon User Library">
<CLASSES>
<root url="file://$MODULE_DIR$/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
<jarDirectory url="file://$MODULE_DIR$/lib" recursive="false" />
</library>
</orderEntry>
</component>
</module>

59
gui/TimeStamper.ipr Normal file
View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project relativePaths="false" version="4">
<component name="ProjectFileVersion" converted="true" />
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/TimeStamper.iml" filepath="$PROJECT_DIR$/TimeStamper.iml" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="libraryTable">
<library name="Griffon 0.3">
<CLASSES>
<root url="jar:///home/jdbernard/programs/griffon/lib/log4j-1.2.15.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/spring-2.5.6.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/ant-trax-1.8.0.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/gant_groovy1.6-1.6.0.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/svnkit-1.2.0.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/junit-4.8.1.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/commons-cli-1.2.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/ant-nodeps-1.8.0.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/groovy-all-1.7.1.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/commons-lang-2.4.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/ant-junit-1.8.0.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/jline-0.9.94.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/ant-launcher-1.8.0.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/commons-logging-1.1.1.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/asm-3.2.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/lib/ant-1.8.0.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/dist/griffon-resources-0.3.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/dist/griffon-cli-0.3.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/dist/griffon-rt-0.3.jar!/" />
<root url="jar:///home/jdbernard/programs/griffon/dist/griffon-scripts-0.3.jar!/" />
</CLASSES>
</library>
</component>
</project>

65
gui/TimeStamper.iws Normal file
View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project relativePaths="false" version="4">
<component name="ProjectPane">
<subPane>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="TimeStamper" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="TimeStamper" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewModuleNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="TimeStamper" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
</PATH>
</subPane>
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="0.5" version="1" splitterProportion="0.5">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<showStructure ProjectPane="false" />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
</navigator>
</component>
<!--
<component name="RunManager" selected="Griffon Application.Griffon:TimeStamper">
<configuration default="false" name="Griffon:TimeStamper" type="GriffonRunConfigurationType" factoryName="Griffon Application">
<module name="TimeStamper" />
<setting name="vmparams" value="" />
<setting name="griffonparams" value="" />
<setting name="hostik" value="localhost" />
<setting name="port" value="8080" />
<setting name="jndi" value="false" />
<setting name="recomp" value="false" />
<setting name="recompileFreq" value="3" />
<setting name="launchBrowser" value="true" />
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Run" />
<method>
<option name="Make" value="true" />
</method>
</configuration>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="Griffon Application.Griffon:TimeStamper" />
</list>
</component>
-->
<component name="ToolWindowManager">
<frame x="10" y="10" width="1260" height="984" extended-state="0" />
<editor active="false" />
<layout>
<window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.25" sideWeight="0.6623068" order="0" side_tool="false" />
</layout>
</component>
</project>

20
gui/TimeStamper.launch Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.codehaus.griffon.GriffonMain"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;TimeStamper&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;TimeStamper&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/TimeStamper&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="TimeStamper"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dbase.dir=${project_loc} -Dgriffon.env=development"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/TimeStamper"/>
</listAttribute>
<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
</launchConfiguration>

73
gui/TimeStamper.tmproj Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>documents</key>
<array>
<dict>
<key>filename</key>
<string>TimeStamper.launch</string>
</dict>
<dict>
<key>filename</key>
<string>build.xml</string>
</dict>
<dict>
<key>name</key>
<string>griffon-app</string>
<key>regexFolderFilter</key>
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
<key>sourceDirectory</key>
<string>griffon-app</string>
</dict>
<dict>
<key>name</key>
<string>test</string>
<key>regexFolderFilter</key>
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
<key>sourceDirectory</key>
<string>test</string>
</dict>
<dict>
<key>name</key>
<string>lib</string>
<key>regexFolderFilter</key>
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
<key>sourceDirectory</key>
<string>lib</string>
</dict>
<dict>
<key>name</key>
<string>scripts</string>
<key>regexFolderFilter</key>
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
<key>sourceDirectory</key>
<string>scripts</string>
</dict>
<dict>
<key>name</key>
<string>src</string>
<key>regexFolderFilter</key>
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
<key>sourceDirectory</key>
<string>src</string>
</dict>
<dict>
<key>name</key>
<string>web-app</string>
<key>regexFolderFilter</key>
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
<key>sourceDirectory</key>
<string>web-app</string>
</dict>
</array>
<key>fileHierarchyDrawerWidth</key>
<integer>200</integer>
<key>metaData</key>
<dict/>
<key>showFileHierarchyDrawer</key>
<true/>
<key>windowFrame</key>
<string>{{237, 127}, {742, 553}}</string>
</dict>
</plist>

10
gui/application.properties Executable file
View File

@ -0,0 +1,10 @@
#Do not edit app.griffon.* properties, they may change automatically. DO NOT put application configuration in here, it is not the right place!
#Thu, 25 Apr 2013 06:40:33 -0500
#Thu Apr 01 22:28:40 CDT 2010
app.version=2.1
app.griffon.version=1.2.0
app.name=TimeStamper
plugins.swing=1.2.0
archetype.default=1.2.0
app.toolkit=swing

97
gui/build.xml Executable file
View File

@ -0,0 +1,97 @@
<project name="timestamper" default="test">
<!-- =================================
target: clean
================================= -->
<target name="clean" description="--> Cleans a Griffon application">
<griffon>
<arg value="clean"/>
</griffon>
</target>
<!-- =================================
target: package
================================= -->
<target name="package" description="--> Packages up Griffon artifacts">
<griffon>
<arg value="package"/>
</griffon>
</target>
<!-- =================================
target: run-app
================================= -->
<target name="run-app" description="--> Run a Griffon application in standalone mode">
<griffon>
<arg value="run-app"/>
</griffon>
</target>
<!-- =================================
target: debug-app
================================= -->
<target name="debug-app" description="--> Run a Griffon application in standalone mode with debugging turned on">
<griffon>
<arg value="run-app"/>
<arg value="-debug"/>
</griffon>
</target>
<!-- =================================
target: run-webstart
================================= -->
<target name="run-webstart" description="--> Run a Griffon application in webstart mode">
<griffon>
<arg value="run-webstart"/>
</griffon>
</target>
<!-- =================================
target: run-applet
================================= -->
<target name="run-applet" description="--> Run a Griffon application in applet mode">
<griffon>
<arg value="run-applet"/>
</griffon>
</target>
<!-- =================================
target: test
================================= -->
<target name="test" description="--> Run a Griffon applications unit tests">
<griffon>
<arg value="test-app"/>
</griffon>
</target>
<!-- =================================
target: dist
================================= -->
<target name="dist" description="--> Packages up Griffon artifacts in the Production Environment">
<griffon>
<arg value="prod"/>
<arg value="package"/>
</griffon>
</target>
<!-- set up the griffon macro -->
<property environment="env"/>
<property name="griffon.home" value="${env.GRIFFON_HOME}"/>
<property name="jdk.home" value="${env.JAVA_HOME}"/>
<condition property="griffon" value="griffon.bat">
<os family="windows"/>
</condition>
<property name="griffon" value="griffon" />
<macrodef name="griffon">
<element name="griffon-args" implicit="yes"/>
<sequential>
<exec executable="${griffon.home}/bin/${griffon}" failonerror="true">
<env key="JAVA_HOME" value="${jdk.home}"/>
<env key="GRIFFON_HOME" value="${griffon.home}"/>
<griffon-args/>
</exec>
</sequential>
</macrodef>
<!-- end set up the griffon macro -->
</project>

BIN
gui/doc/feed.pdf Executable file

Binary file not shown.

77
gui/doc/feed.rst Executable file
View File

@ -0,0 +1,77 @@
Centralized vs. decentralized
-----------------------------
Centralized
```````````
- one central list
- remote apps that sync with central?
Decentralized
`````````````
- sync to URL(s)?
- need a network protocol
- HTTP?
- SSL?
- group-wise sync?
- establish master/slaves?
- easier than coordinated group-update:::
map each URL to synch -> the last time updated.
if (update_period):
forall URLs: synch
else if (incoming_update):
forall (URLs older than incoming update): synch
- synch based on hash of updates?
- need canonicalizer for text. Use XML?
- hash algorithm:::
SHA-1 of:
concatenate:
date (YYYYMMDDhhmmssSSS)
name
notes
External Feeds
--------------
Item format
```````````
- time started
- name/description
- notes
- category?
Pull from
`````````
- needs to be optional
- standardized input format
- easy to parse
- no errors, false positives
- restrictive.
- flexible input format
- matches regex's?
- map groups to fields
Push to
```````
- optional
- standardized output
- cannot be flexible to match output medium
- flexible input format
- choose fields and format values

View File

@ -0,0 +1,8 @@
# sync-options:
# LOCAL TIMELINE: this file
# SYNC TO: ssh://jdbernard@jdbernard.no-ip.org/timelines/jdbernard.timeline.txt
# SYNC TO: http://www.twitter.com/jdbernard
# pull only
# update every 30 sec
# SYNC TO: file:///home/jdbernard/timelines/jdbernard.timeline.bak
# push only

BIN
gui/doc/uml.tsm Normal file

Binary file not shown.

View File

@ -0,0 +1,27 @@
application {
title="TimeStamper"
startupGroups=["TimeStamperMain"]
autoShutdown=true
}
mvcGroups {
LogDialog {
model="com.jdblabs.timestamper.gui.LogDialogModel"
controller="com.jdblabs.timestamper.gui.LogDialogController"
view="com.jdblabs.timestamper.gui.LogDialogView"
}
TimeStamperMain {
model="com.jdblabs.timestamper.gui.TimeStamperMainModel"
view="com.jdblabs.timestamper.gui.TimeStamperMainView"
controller="com.jdblabs.timestamper.gui.TimeStamperMainController"
}
PunchcardDialog {
model="com.jdblabs.timestamper.gui.PunchcardDialogModel"
view="com.jdblabs.timestamper.gui.PunchcardDialogView"
controller="com.jdblabs.timestamper.gui.PunchcardDialogController"
}
NotesDialog {
model="com.jdblabs.timestamper.gui.NotesDialogModel"
view="com.jdblabs.timestamper.gui.NotesDialogView"
controller="com.jdblabs.timestamper.gui.NotesDialogController"
}
}

View File

@ -0,0 +1,191 @@
// key signing information
environments {
development {
signingkey {
params {
// sigfile = 'GRIFFON'
// keystore = "${basedir}/griffon-app/conf/keys/devKeystore"
// alias = 'development'
storepass = 'BadStorePassword'
keypass = 'BadKeyPassword'
lazy = true // only sign when unsigned
}
}
}
test {
griffon {
jars {
sign = false
pack = false
}
}
}
production {
signingkey {
params {
// NOTE: for production keys it is more secure to rely on key prompting
// no value means we will prompt //storepass = 'BadStorePassword'
// no value means we will prompt //keypass = 'BadKeyPassword'
lazy = false // sign, regardless of existing signatures
}
}
griffon {
jars {
sign = true
pack = true
destDir = "${basedir}/staging"
}
webstart {
codebase = 'CHANGE ME'
}
}
}
}
griffon {
memory {
//max = '64m'
//min = '2m'
//minPermSize = '2m'
//maxPermSize = '64m'
}
jars {
sign = false
pack = false
destDir = "${basedir}/staging"
jarName = "${appName}.jar"
}
extensions {
jarUrls = []
jnlpUrls = []
/*
props {
someProperty = 'someValue'
}
resources {
linux { // windows, macosx, solaris
jars = []
nativelibs = []
props {
someProperty = 'someValue'
}
}
}
*/
}
webstart {
codebase = "${new File(griffon.jars.destDir).toURI().toASCIIString()}"
jnlp = 'application.jnlp'
}
applet {
jnlp = 'applet.jnlp'
html = 'applet.html'
}
}
// required for custom environments
signingkey {
params {
def env = griffon.util.Environment.current.name
sigfile = 'GRIFFON-' + env
keystore = "${basedir}/griffon-app/conf/keys/${env}Keystore"
alias = env
// storepass = 'BadStorePassword'
// keypass = 'BadKeyPassword'
lazy = true // only sign when unsigned
}
}
griffon {
doc {
logo = '<a href="http://griffon-framework.org" target="_blank"><img alt="The Griffon Framework" src="../img/griffon.png" border="0"/></a>'
sponsorLogo = "<br/>"
footer = "<br/><br/>Made with Griffon (@griffon.version@)"
}
}
deploy {
application {
title = "${appName} ${appVersion}"
vendor = System.properties['user.name']
homepage = "http://localhost/${appName}"
description {
complete = "${appName} ${appVersion}"
oneline = "${appName} ${appVersion}"
minimal = "${appName} ${appVersion}"
tooltip = "${appName} ${appVersion}"
}
icon {
'default' {
name = 'griffon-icon-64x64.png'
width = '64'
height = '64'
}
splash {
name = 'griffon.png'
width = '391'
height = '123'
}
selected {
name = 'griffon-icon-64x64.png'
width = '64'
height = '64'
}
disabled {
name = 'griffon-icon-64x64.png'
width = '64'
height = '64'
}
rollover {
name = 'griffon-icon-64x64.png'
width = '64'
height = '64'
}
shortcut {
name = 'griffon-icon-64x64.png'
width = '64'
height = '64'
}
}
}
}
griffon.project.dependency.resolution = {
// inherit Griffon' default dependencies
inherits("global") {
}
log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
repositories {
griffonHome()
// uncomment the below to enable remote dependency resolution
// from public Maven repositories
//mavenLocal()
//mavenCentral()
//mavenRepo "http://snapshots.repository.codehaus.org"
//mavenRepo "http://repository.codehaus.org"
//mavenRepo "http://download.java.net/maven/2/"
//mavenRepo "http://repository.jboss.com/maven2/"
}
dependencies {
// specify dependencies here under either 'build', 'compile', 'runtime' or 'test' scopes eg.
// runtime 'mysql:mysql-connector-java:5.1.5'
}
}
log4j = {
// Example of changing the log pattern for the default console
// appender:
appenders {
console name: 'stdout', layout: pattern(conversionPattern: '%d [%t] %-5p %c - %m%n')
}
error 'org.codehaus.griffon',
'org.springframework',
'org.apache.karaf',
'groovyx.net'
warn 'griffon'
}

View File

@ -0,0 +1,7 @@
root {
'groovy.swing.SwingBuilder' {
controller = ['Threading']
view = '*'
}
}

View File

@ -0,0 +1,15 @@
log4j = {
// Example of changing the log pattern for the default console
// appender:
appenders {
console name: 'stdout', layout: pattern(conversionPattern: '%d [%t] %-5p %c - %m%n')
}
error 'org.codehaus.griffon'
info 'griffon.util',
'griffon.core',
'griffon.@application.toolkit@',
'griffon.app'
}

View File

@ -0,0 +1,5 @@
import org.slf4j.LoggerFactory
onNewInstance = { klass, type, instance ->
instance.metaClass.logger = LoggerFactory.getLogger(klass.name)
}

Binary file not shown.

View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
</head>
<body>
<script src="http://java.com/js/deployJava.js"></script>
<script>
var attributes = {id: 'TimeStamper',
codebase:'@griffonAppCodebase@',
code:'@griffonAppletClass@',
archive:'@appletJars@',
width:'240', height:'320'} ;
var parameters = {fontSize:16,
java_arguments: "-Djnlp.packEnabled=true",
jnlp_href:'@griffonAppCodebase@/applet.jnlp',
draggable:'true',
image:'griffon.png',
boxmessage:'Loading TimeStamper',
boxbgcolor:'#FFFFFF', boxfgcolor:'#000000',
codebase_lookup: 'false'} ;
var version = '1.5.0' ;
deployJava.runApplet(attributes, parameters, version);
</script>
<!--
<APPLET CODEBASE='@griffonAppCodebase@'
CODE='@griffonAppletClass@'
ARCHIVE='@appletJars@'
WIDTH='240' HEIGHT='320'>
<PARAM NAME="java_arguments" VALUE="-Djnlp.packEnabled=true">
<PARAM NAME='jnlp_href' VALUE='@griffonAppCodebase@/applet.jnlp'>
<PARAM NAME='dragggable' VALUE='true'>
<PARAM NAME='image' VALUE='griffon.png'>
<PARAM NAME='boxmessage' VALUE='Loading TimeStamper'>
<PARAM NAME='boxbgcolor' VALUE='#FFFFFF'>
<PARAM NAME='boxfgcolor' VALUE='#000000'>
<PARAM NAME='codebase_lookup' VALUE='false'>
</APPLET>
-->
</body>
</html>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE jnlp SYSTEM "http://java.sun.com/dtd/JNLP-1.5.dtd">
<jnlp
version="2.1"
codebase="@griffonAppCodebase@"
href="@jnlpFileName@"
>
<information>
<title>TimeStamper</title>
<vendor>TimeStamper</vendor>
<!--<homepage href="http://app.example.com/"/>-->
<!--fallback description-->
<description>TimeStamper</description>
<description kind="one-line">TimeStamper</description>
<description kind="short">TimeStamper</description>
<description kind="tooltip">TimeStamper</description>
<!-- fallback icon -->
<icon href="griffon-icon-48x48.png" kind="default" width="48" height="48"/>
<!-- icon used for splash screen -->
<icon href="griffon.png" kind="splash" width="381" height="123"/>
<!-- icon used in menu -->
<icon href="griffon-icon-16x16.png" kind="shortcut" width="16" height="16"/>
<!-- icon used on desktop -->
<icon href="griffon-icon-32x32.png" kind="shortcut" width="32" height="32"/>
<!-- to create shortcuts, uncomment this
<shortcut online="true">
<desktop/>
<menu submenu="TimeStamper"/>
</shortcut>
-->
<offline-allowed/>
</information>
<security>
<all-permissions/>
<!--<j2ee-application-client-permissions/>-->
</security>
<resources>
<property name="jnlp.packEnabled" value="true"/>
<j2se version="1.5+" @memoryOptions@/>
<!-- auto-added jars follow, griffon-rt, app, and groovy -->
@jnlpJars@
<!-- Add all extra jars below here, or the app may break -->
@jnlpExtensions@
</resources>
<applet-desc
documentbase="@griffonAppCodebase@"
name="TimeStamperApplet"
main-class="@griffonAppletClass@"
width="320"
height="640">
<!-- params are ignored when referenced from web page for 6u10 -->
<!--<param name="key1" value="value1"/>-->
<!--<param name="key2" value="value2"/>-->
</applet-desc>
</jnlp>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE jnlp SYSTEM "http://java.sun.com/dtd/JNLP-1.5.dtd">
<jnlp
version="2.1"
codebase="@griffonAppCodebase@"
href="@jnlpFileName@"
>
<information>
<title>TimeStamper</title>
<vendor>TimeStamper</vendor>
<!--<homepage href="http://app.example.com/"/>-->
<!--fallback description-->
<description>TimeStamper</description>
<description kind="one-line">TimeStamper</description>
<description kind="short">TimeStamper</description>
<description kind="tooltip">TimeStamper</description>
<!-- fallback icon -->
<icon href="griffon-icon-48x48.png" kind="default" width="48" height="48"/>
<!-- icon used for splash screen -->
<icon href="griffon.png" kind="splash" width="381" height="123"/>
<!-- icon used in menu -->
<icon href="griffon-icon-16x16.png" kind="shortcut" width="16" height="16"/>
<!-- icon used on desktop -->
<icon href="griffon-icon-32x32.png" kind="shortcut" width="32" height="32"/>
<!-- to create shortcuts, uncomment this
<shortcut online="true">
<desktop/>
<menu submenu="TimeStamper"/>
</shortcut>
<offline-allowed/>
-->
</information>
<security>
<all-permissions/>
<!--<j2ee-application-client-permissions/>-->
</security>
<resources>
<property name="jnlp.packEnabled" value="true"/>
<j2se version="1.5+" @memoryOptions@/>
<!-- auto-added jars follow, griffon-rt, app, and groovy -->
@jnlpJars@
<!-- Add all extra jars below here, or the app may break -->
@jnlpExtensions@
</resources>
<application-desc main-class="@griffonApplicationClass@">
<!-- params are ignored when referenced from web page for 6u10 -->
<!--<param name="key1" value="value1"/>-->
<!--<param name="key2" value="value2"/>-->
</application-desc>
</jnlp>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,16 @@
package com.jdblabs.timestamper.gui
class LogDialogController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
}
/*
def action = { evt = null ->
}
*/
}

View File

@ -0,0 +1,18 @@
package com.jdblabs.timestamper.gui
import java.awt.Point
class NotesDialogController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
def loc = model.mainMVC.view.frame.location
Point p = new Point(0, (int) model.mainMVC.view.frame.bounds.height + 50)
p.translate((int) loc.x, (int) loc.y)
view.dialog.location = p
}
}

View File

@ -0,0 +1,22 @@
package com.jdblabs.timestamper.gui
import java.awt.Point
class PunchcardDialogController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
def loc = model.mainMVC.view.frame.location
Point p = new Point(0, (int) model.mainMVC.view.frame.bounds.height + 50)
p.translate((int) loc.x, (int) loc.y)
view.dialog.location = p
}
/*
def action = { evt = null ->
}
*/
}

View File

@ -0,0 +1,168 @@
package com.jdblabs.timestamper.gui
import com.jdbernard.util.SmartConfig
import com.jdblabs.timestamper.core.TimelineMarker
import com.jdblabs.timestamper.core.TimelineProperties
class TimeStamperMainController {
// these will be injected by Griffon
def model
def view
def thisMVC
def syncTimers = [:]
void mvcGroupInit(Map args) {
def configFile
logger.trace("Initializing TimeStamperMain MVC...")
// init mvc map
thisMVC = ['model': model, 'view': view, 'controller': this]
model.notesDialogMVC = buildMVCGroup('NotesDialog', 'notesDialog',
'mainMVC': thisMVC)
model.punchcardDialogMVC = buildMVCGroup('PunchcardDialog',
'punchcardDialog', 'mainMVC': thisMVC)
// load application properties
String userHomeDir = System.getProperty('user.home')
configFile = new File(userHomeDir, ".timestamperrc")
logger.trace("Reading configuration from {}", configFile.canonicalPath)
model.config = new SmartConfig(configFile)
// load the last used timeline file
String lastUsed = model.config.lastUsed
if (lastUsed == "") {
lastUsed = 'timeline.default.properties'
model.config.setProperty('lastUsed', lastUsed)
}
// load the plugin directory
File pluginDir = model.config.getProperty("pluginDir", new File("plugins"))
if (!pluginDir.exists()) pluginDir.mkdirs()
logger.trace("Adding plugin classpath: '{}'", pluginDir.canonicalPath)
def pluginLoader = new GroovyClassLoader(this.class.classLoader)
pluginLoader.addURL(pluginDir.toURI().toURL())
pluginDir.eachFileMatch(/.*\.jar/) { jarfile ->
pluginLoader.addURL(jarfile.toURI().toURL()) }
// instantiate plugins
model.config."plugin.classes".split(',').each { className -> try {
if (className.trim() == "") return
def pluginClass = pluginLoader.loadClass(className)
model.plugins <<pluginClass.newInstance()
logger.trace("Loaded plugin: {}", className)
} catch (ClassNotFoundException cnfe) {
logger.warn("Unable to load plugin ${className}: " +
"class not found.", cnfe)
} catch (Throwable t) {
logger.warn("Unable to load plugin ${className}.", t)
} }
// run plugin startup hook
model.plugins.each { plugin ->
wrapPluginCall { plugin.onStartup(thisMVC) } }
logger.trace("Reading Timeline properties from '{}'", lastUsed)
load(new File(lastUsed))
}
def wrapPluginCall(Closure c) {
try { c() } catch (Throwable t) {
logger.warn("Plugin threw an exception in ${functionName} " +
"when passed ${param}:", t)
return false
}
}
def load = { File propertiesFile ->
// pass through plugins
propertiesFile = model.plugins.inject(propertiesFile) { file, plugin ->
// call plugin
def ret = wrapPluginCall { plugin.onTimelineLoad(thisMVC, file) }
// if the plugin call succeeded, pass the result, else pass
// the last one
ret ? ret : file
}
try {
model.config.lastUsed = propertiesFile.canonicalPath
} catch (IOException ioe) { logger.error(ioe) }
// load the properties file
model.timelineProperties = new TimelineProperties(propertiesFile)
// load the main timeline
model.timeline = model.timelineProperties.timeline
// load the last marker
model.currentMarker = model.timeline.getLastMarker(new Date())
}
def saveas = {
}
def exitGracefully = { evt = null ->
// hide the frame immediately
view.frame.visible = false
logger.trace("Exiting gracefully.")
// save config
logger.debug("Config: {}", model.config)
model.config.save()
// save timeline and properties
model.timelineProperties.save()
// call plugin exit hooks
model.plugins.each { plugin ->
wrapPluginCall { plugin.onExit(thisMVC) } }
logger.trace("Completed graceful shutdown.")
app.shutdown()
}
def newTask = { mark, notes = "No comments.", timestamp = new Date() ->
TimelineMarker tm = new TimelineMarker(timestamp, mark, notes)
// pass through the plugins
tm = model.plugins.inject(tm) { marker, plugin ->
def ret = wrapPluginCall { plugin.onNewTask(thisMVC, marker) }
ret ? ret : marker
}
model.timeline.addMarker(tm)
model.currentMarker = model.timeline.getLastMarker(new Date())
// auto-persist if enabled
if (model.timelineProperties.persistOnUpdate)
model.timelineProperties.save()
}
def deleteTask = { marker ->
// pass through the plugins
model.plugins.each { plugin ->
wrapPluginCall { plugin.onDeleteTask(thisMVC, marker) } }
model.timeline.removeMarker(marker)
model.currentMarker = model.timeline.getLastMarker(new Date())
// auto-persist if enabled
if (model.timelineProperties.persistOnUpdate)
model.timelineProperties.save()
}
}

View File

View File

@ -0,0 +1,22 @@
/*
* This script is executed inside the EDT, so be sure to
* call long running code in another thread.
*
* You have the following options
* - SwingBuilder.doOutside { // your code }
* - Thread.start { // your code }
* - SwingXBuilder.withWorker( start: true ) {
* onInit { // initialization (optional, runs in current thread) }
* work { // your code }
* onDone { // finish (runs inside EDT) }
* }
*
* You have the following options to run code again inside EDT
* - SwingBuilder.doLater { // your code }
* - SwingBuilder.edt { // your code }
* - SwingUtilities.invokeLater { // your code }
*/
import groovy.swing.SwingBuilder
SwingBuilder.lookAndFeel('system', 'nimbus', ['metal', [boldFonts: false]])

View File

@ -0,0 +1,18 @@
/*
* This script is executed inside the EDT, so be sure to
* call long running code in another thread.
*
* You have the following options
* - SwingBuilder.doOutside { // your code }
* - Thread.start { // your code }
* - SwingXBuilder.withWorker( start: true ) {
* onInit { // initialization (optional, runs in current thread) }
* work { // your code }
* onDone { // finish (runs inside EDT) }
* }
*
* You have the following options to run code again inside EDT
* - SwingBuilder.doLater { // your code }
* - SwingBuilder.edt { // your code }
* - SwingUtilities.invokeLater { // your code }
*/

View File

@ -0,0 +1,18 @@
/*
* This script is executed inside the EDT, so be sure to
* call long running code in another thread.
*
* You have the following options
* - SwingBuilder.doOutside { // your code }
* - Thread.start { // your code }
* - SwingXBuilder.withWorker( start: true ) {
* onInit { // initialization (optional, runs in current thread) }
* work { // your code }
* onDone { // finish (runs inside EDT) }
* }
*
* You have the following options to run code again inside EDT
* - SwingBuilder.doLater { // your code }
* - SwingBuilder.edt { // your code }
* - SwingUtilities.invokeLater { // your code }
*/

View File

@ -0,0 +1,19 @@
/*
* This script is executed inside the EDT, so be sure to
* call long running code in another thread.
*
* You have the following options
* - SwingBuilder.doOutside { // your code }
* - Thread.start { // your code }
* - SwingXBuilder.withWorker( start: true ) {
* onInit { // initialization (optional, runs in current thread) }
* work { // your code }
* onDone { // finish (runs inside EDT) }
* }
*
* You have the following options to run code again inside EDT
* - SwingBuilder.doLater { // your code }
* - SwingBuilder.edt { // your code }
* - SwingUtilities.invokeLater { // your code }
*/

View File

View File

@ -0,0 +1,7 @@
package com.jdblabs.timestamper.gui
import groovy.beans.Bindable
class LogDialogModel {
def mainMVC
}

View File

@ -0,0 +1,9 @@
package com.jdblabs.timestamper.gui
import groovy.beans.Bindable
class NotesDialogModel {
// needs to be injected by buildMVCGroup call
def mainMVC
}

View File

@ -0,0 +1,9 @@
package com.jdblabs.timestamper.gui
import groovy.beans.Bindable
class PunchcardDialogModel {
// needs to be injected by buildMVCGroup() call
def mainMVC
}

View File

@ -0,0 +1,25 @@
package com.jdblabs.timestamper.gui
import groovy.beans.Bindable
import java.awt.Point
import java.awt.Rectangle
import java.util.Properties
import com.jdbernard.util.SmartConfig
import com.jdblabs.timestamper.core.Timeline
import com.jdblabs.timestamper.core.TimelineMarker
import com.jdblabs.timestamper.core.TimelineProperties
class TimeStamperMainModel {
@Bindable TimelineMarker currentMarker
@Bindable Timeline timeline
@Bindable TimelineProperties timelineProperties
SmartConfig config
List plugins = []
def notesDialogMVC
def punchcardDialogMVC
@Bindable Point absoluteLocation
@Bindable Rectangle frameSize
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,20 @@
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.FileAppender
import static ch.qos.logback.classic.Level.*
appender("CONSOLE", ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%level - %msg%n"
}
}
appender("FILE", FileAppender) {
file="timestamper.log"
encoder(PatternLayoutEncoder) {
pattern = "%date %level %logger - %msg%n"
}
}
root(WARN, ["CONSOLE"])
logger("com.jdblabs.*", TRACE, ["FILE"], false)

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

1880
gui/griffon-app/session.vim Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
package com.jdblabs.timestamper.gui
import javax.swing.JDialog
dialog = dialog(new JDialog(model.mainMVC.view.frame),
title: 'Error Messages...',
modal: false) {
logger.trace( "Building LogDialog view." )
}

View File

@ -0,0 +1,65 @@
package com.jdblabs.timestamper.gui
import java.awt.Color
import java.awt.Point
import java.awt.Rectangle
import java.awt.Toolkit
import javax.swing.BoxLayout
import javax.swing.JDialog
import net.miginfocom.swing.MigLayout
Point mousePressRelativeToDialog
Point offsetFromMainFrame = new Point(0,0)
boolean coupledToMainFrame = false
mousePressed = { evt -> mousePressRelativeToDialog = evt?.point }
mouseDragged = { evt ->
GUIUtil.componentDragged(dialog, evt, mousePressRelativeToDialog,
new Rectangle(Toolkit.defaultToolkit.screenSize),
model.mainMVC.view.frame.bounds)
offsetFromMainFrame = GUIUtil.calculateOffset(
model.mainMVC.view.frame, dialog)
coupledToMainFrame = GUIUtil.componentsCoupled(dialog,
model.mainMVC.view.frame);
}
dialog = dialog(new JDialog(model.mainMVC.view.frame),
title: 'Notes',
modal: false,
undecorated: true,
minimumSize: [325, 200],
mousePressed: mousePressed,
mouseDragged: mouseDragged,
iconImage: imageIcon('/16-em-pencil.png').image,
iconImages: [imageIcon('/16-em-pencil.png').image],
location: bind(source: model.mainMVC.model,
sourceProperty: 'absoluteLocation',
converter: { loc ->
if (coupledToMainFrame) {
Point p = new Point(offsetFromMainFrame)
p.translate((int) loc.x, (int) loc.y)
return p
} else return dialog.location })
) {
logger.trace('Building NotesDialog GUI')
panel(
border:lineBorder(color: Color.BLACK, thickness:1, parent:true),
layout: new MigLayout('insets 10 10 10 10, fill')
) {
scrollPane(constraints: 'growx, growy') {
notesTextArea = textArea(lineWrap: true, columns: 20, rows: 5,
wrapStyleWord: true,
text: bind(source: model.mainMVC.model,
sourceProperty: 'currentMarker',
sourceValue: { model.mainMVC.model.currentMarker?.notes}),
keyReleased: {
if (model.mainMVC.model.currentMarker != null)
model.mainMVC.model.currentMarker.notes =
notesTextArea.text})
}
}
}

View File

@ -0,0 +1,164 @@
package com.jdblabs.timestamper.gui
import java.awt.Color
import java.awt.Point
import java.awt.Toolkit
import java.awt.Rectangle
import java.beans.PropertyChangeListener
import java.text.SimpleDateFormat
import javax.swing.JDialog
import java.util.Calendar
import javax.swing.SwingConstants
import com.jdblabs.timestamper.gui.TimelineDayDisplay
import com.toedter.calendar.JDateChooser
import net.miginfocom.swing.MigLayout
Point mousePressRelativeToDialog
Point offsetFromMainFrame = new Point(0,0)
boolean coupledToMainFrame = false
dateFormatter = new SimpleDateFormat("EEE MMM dd")
mousePressed = { evt -> mousePressRelativeToDialog = evt?.point }
mouseDragged = { evt ->
GUIUtil.componentDragged(dialog, evt, mousePressRelativeToDialog,
new Rectangle(Toolkit.defaultToolkit.screenSize),
model.mainMVC.view.frame.bounds)
offsetFromMainFrame = GUIUtil.calculateOffset(
model.mainMVC.view.frame, dialog)
coupledToMainFrame = GUIUtil.componentsCoupled(dialog,
model.mainMVC.view.frame);
}
dialog = dialog(new JDialog(model.mainMVC.view.frame),
title: 'Punchcard',
modal: false,
undecorated: true,
mousePressed: mousePressed,
mouseDragged: mouseDragged,
iconImage: imageIcon('/16-file-archive.png').image,
iconImages: [imageIcon('/16-file-archive.png').image],
minimumSize: [450, 500],
location: bind(source: model.mainMVC.model,
sourceProperty: 'absoluteLocation',
converter: { loc ->
if (coupledToMainFrame) {
Point p = new Point(offsetFromMainFrame)
p.translate((int) loc.x, (int) loc.y)
return p
} else return dialog.location })
) {
logger.trace('Building PunchcardDialog GUI')
panel(
border:lineBorder(color: Color.BLACK, thickness:1, parent:true),
layout: new MigLayout('fill, insets 10 10 10 10',
'[grow]', '[grow]10[grow 0]')
) {
dayDisplay = widget(constraints: 'grow, gp 200',
new TimelineDayDisplay(model.mainMVC.model.timeline),
timeline: bind(source: model.mainMVC.model,
sourceProperty: 'timeline'))
model.mainMVC.model.addPropertyChangeListener('currentMarker', {
dayDisplay.stateChanged(null) } as PropertyChangeListener)
panel(
border: lineBorder(color: Color.BLACK, thickness: 1),
constraints: 'growx, growy 0, newline, bottom',
layout: new MigLayout('fill, insets 0 10 0 10',
'[grow]10[grow 0]', '[][][grow 0]')
) {
dateChooser = widget(new JDateChooser(),
constraints: 'growx, gaptop 10',
dateFormatString: 'MMM d, yyyy HH:mm',
date: bind(source: dayDisplay,
sourceProperty: 'selectedTimelineMarker',
converter: { it?.timestamp }))
panel(constraints: 'spany 3, wrap, al trailing',
layout: new MigLayout('insets 0', '[]0[]0[]0[]0[]',
'[]0[]0[]0[]0[]')
) {
label(background: [255, 255, 153],
constraints: 'growx, spanx 5, wrap',
horizontalAlignment: SwingConstants.CENTER,
opaque: true,
text: bind(source: dayDisplay,
sourceProperty: 'rangeStart',
converter: { dateFormatter.format(it) }))
Calendar calendar = Calendar.getInstance()
button(icon: imageIcon('/previous-week.png'),
border: emptyBorder(4),
actionPerformed: {
calendar.time = dayDisplay.rangeStart
calendar.add(Calendar.WEEK_OF_YEAR, -1)
dayDisplay.day = calendar.time })
button(icon: imageIcon('/previous-day.png'),
border: emptyBorder(4),
actionPerformed: {
calendar.time = dayDisplay.rangeStart
calendar.add(Calendar.DAY_OF_YEAR, -1)
dayDisplay.day = calendar.time })
button(text: 'Today',
border: emptyBorder(4),
actionPerformed: {
calendar = Calendar.getInstance()
dayDisplay.day = calendar.time })
button(icon: imageIcon('/next-day.png'),
border: emptyBorder(4),
actionPerformed: {
calendar.time = dayDisplay.rangeStart
calendar.add(Calendar.DAY_OF_YEAR, 1)
dayDisplay.day = calendar.time })
button(icon: imageIcon('/next-week.png'),
constraints: 'wrap',
border: emptyBorder(4),
actionPerformed: {
calendar.time = dayDisplay.rangeStart
calendar.add(Calendar.WEEK_OF_YEAR, 1)
dayDisplay.day = calendar.time })
button(text: 'New Marker', icon: imageIcon('/new-marker.png'),
constraints: 'growx, spanx 5, wrap',
horizontalAlignment: SwingConstants.CENTER,
actionPerformed: {
model.mainMVC.controller.newTask(markTextField.text,
notesTextField.text, dateChooser.date) })
button(text: 'Delete Marker',
icon: imageIcon('/delete-marker.png'),
constraints: 'growx, spanx 5, wrap',
horizontalAlignment: SwingConstants.CENTER,
actionPerformed: {
model.mainMVC.controller.deleteTask(
dayDisplay.selectedTimelineMarker) })
button(text: 'Apply Changes',
icon: imageIcon('/document-save-16x16.png'),
constraints: 'growx, spanx 5',
horizontalAlignment: SwingConstants.CENTER,
actionPerformed: {
Date d = dateChooser.date
String m = markTextField.text
String n = notesTextField.text
model.mainMVC.controller.deleteTask(
dayDisplay.selectedTimelineMarker)
model.mainMVC.controller.newTask(m, n, d) })
}
markTextField = textField(constraints: 'growx, wrap',
text: bind(source: dayDisplay,
sourceProperty: 'selectedTimelineMarker',
converter: { it?.mark }))
scrollPane(constraints: 'growx, gapbottom 10',
minimumSize: [0, 50]) {
notesTextField = textArea( wrapStyleWord: true, lineWrap: true,
text: bind(source: dayDisplay,
sourceProperty: 'selectedTimelineMarker',
converter: { it?.notes }))
}
}
}
}

View File

@ -0,0 +1,205 @@
package com.jdblabs.timestamper.gui
import java.awt.Color
import java.awt.Font
import java.awt.Point
import java.awt.Rectangle
import java.awt.Toolkit
import java.awt.event.KeyEvent
import javax.swing.BoxLayout
import javax.swing.JDialog
import javax.swing.JFileChooser
import javax.swing.SwingConstants
import javax.swing.Timer
import com.jdblabs.timestamper.core.Timeline
import net.miginfocom.swing.MigLayout
/* ========== *
* GUI Events *
* ========== */
taskTextFieldChanged = { evt = null ->
if (evt.keyCode == KeyEvent.VK_ENTER) {
taskTextField.font = taskBoldFont
controller.newTask(taskTextField.text)
}
else if (evt.keyCode == KeyEvent.VK_ESCAPE) {
taskTextField.font = taskBoldFont
taskTextField.text = model.currentMarker.mark
}
else if (!evt.isActionKey())
taskTextField.font = taskThinFont
}
showNotes = { evt = null ->
model.notesDialogMVC.view.dialog.visible = notesVisibleButton.selected
}
showPunchcard = { evt = null ->
model.punchcardDialogMVC.view.dialog.visible = punchcardVisibleButton.selected
}
mousePressed = { evt = null ->
mousePressRelativeToFrame = evt?.point
}
mouseDragged = { evt = null ->
GUIUtil.componentDragged(frame, evt, mousePressRelativeToFrame,
new Rectangle(Toolkit.defaultToolkit.screenSize))
}
/* ============== *
* GUI Definition *
* ============== */
updateTimer = new Timer(1000, action(name: 'GUI Refresh', closure: {
Date currentTime = new Date()
currentTimeLabel.text = Timeline.shortFormat.format(currentTime)
if (model.currentMarker != null) {
long seconds = currentTime.time - model.currentMarker.timestamp.time
seconds /= 1000
long minutes = seconds / 60
seconds = seconds % 60
long hours = minutes / 60
minutes %= 60
long days = hours / 24
hours %= 24
StringBuilder sb = new StringBuilder()
if (days > 0) sb.append(days + "day ")
if (hours > 0) sb.append(hours + "hr ")
if (minutes > 0) sb.append(minutes + "min ")
sb.append(seconds + "sec")
totalTimeLabel.text = sb.toString()
} else totalTimeLabel.text = ""
}))
updateTimer.start()
optionsMenu = popupMenu() {
menuItem(icon: imageIcon('/document-save-16x16.png'), text: 'Save Timeline',
actionPerformed: { model.timelineProperties.save() })
menuItem(icon: imageIcon('/document-save-as-16x16.png'),
text: 'Save a new copy...', actionPerformed: controller.&saveas)
menuItem(icon: imageIcon('/document-open-16x16.png'),
text: 'Load Timeline...', actionPerformed: {
if (fileDialog.showOpenDialog(frame) ==
JFileChooser.APPROVE_OPTION)
controller.load(fileDialog.selectedFile) })
checkBoxMenuItem(text: 'Save on update?',
selected: bind(source: model, sourceProperty: 'timelineProperties',
sourceValue: { model.timelineProperties?.persistOnUpdate }),
actionPerformed: {
model.timelineProperties.persistOnUpdate = it.source.selected })
aboutMenuItem = checkBoxMenuItem(text: 'About...',
actionPerformed: { aboutDialog.visible = aboutMenuItem.selected })
}
fileDialog = fileChooser();
frame = application(title:'TimeStamper',
location:[50,50],
locationByPlatform:true,
minimumSize: [325, 0],
pack:true,
undecorated:true,
iconImage: imageIcon('/appointment-new-32x32.png').image,
iconImages: [imageIcon('/appointment-new-32x32.png').image,
imageIcon('/appointment-new-16x16.png').image],
componentMoved: { evt -> model.absoluteLocation = frame.location }
) {
logger.trace('Building TimeStamperMain GUI')
panel(
border:lineBorder(color:Color.BLACK, thickness:1, parent:true),
layout: new MigLayout('insets 0 5 0 0, fill','', '[]0[]0[]'),
mousePressed: mousePressed,
mouseDragged: mouseDragged
) {
def mainFont = new Font(Font.SANS_SERIF, Font.BOLD, 12)
def timeFont = new Font(Font.SANS_SERIF, Font.BOLD, 14)
label("Current task started at ", font: mainFont)
label(constraints: 'align leading', font: timeFont,
foreground: [0, 102, 102],
text: bind(source: model, sourceProperty: 'currentMarker',
sourceValue: {
model.currentMarker == null ? "00:00:00" :
Timeline.shortFormat.format(model.currentMarker.timestamp)
}))
panel(constraints: 'alignx trailing, aligny top, wrap') {
boxLayout(axis: BoxLayout.X_AXIS)
button(mouseClicked: { evt ->
optionsMenu.show(evt.component, evt.x, evt.y) },
icon: imageIcon('/16-tool-a.png'),
rolloverIcon: imageIcon('/16-tool-a-hover.png'),
border: emptyBorder(0),
contentAreaFilled: false,
hideActionText: true,
toolTipText: 'Options Menu')
button(actionPerformed: controller.&exitGracefully,
icon: imageIcon('/16-em-cross.png'),
rolloverIcon: imageIcon('/16-em-cross-hover.png'),
border: emptyBorder(0),
contentAreaFilled: false,
hideActionText: true,
toolTipText: 'Close Application')
}
taskTextField = textField("Task name",
constraints: "growx, span 2, w 250::",
keyReleased: taskTextFieldChanged,
toolTipText: 'The current task',
text: bind(source: model, sourceProperty: 'currentMarker',
sourceValue: { model.currentMarker?.mark }))
taskThinFont = taskTextField.font
taskBoldFont = taskTextField.font.deriveFont(Font.BOLD)
panel(constraints: 'alignx leading, aligny top, gapright 5px, wrap') {
boxLayout(axis: BoxLayout.X_AXIS)
notesVisibleButton = toggleButton(
actionPerformed: showNotes,
icon: imageIcon('/16-em-pencil.png'),
hideActionText: true,
border: emptyBorder(4),
toolTipText: 'Show/hide task notes.')
punchcardVisibleButton = toggleButton(
actionPerformed: showPunchcard,
icon: imageIcon('/16-file-archive.png'),
hideActionText: true,
border: emptyBorder(4),
toolTipText: 'Show/hide the timeline display.')
}
totalTimeLabel = label("", constraints: 'alignx leading',
font: timeFont, foreground: [0, 153, 0])
currentTimeLabel = label("00:00:00", constraints: 'align trailing',
font: timeFont, foreground: [204, 0, 0])
}
}
aboutDialog = dialog(new JDialog(frame),
visible: false,
locationRelativeTo: null,
pack: true,
undecorated: true,
title: "About TimeStamper v" + app.metadata.'app.version'
) {
panel(layout: new MigLayout('fill'),
border: lineBorder(color: Color.BLACK, thickness: 1)) {
label(font: new Font(Font.SANS_SERIF, Font.PLAIN, 18),
text: "TimeStamper", constraints: 'growx, wrap',
horizontalAlignment: SwingConstants.CENTER)
label(text: "version " + app.metadata.'app.version'
+ " by Jonathan Bernard", constraints: 'growx, wrap',
horizontalAlignment: SwingConstants.CENTER)
textField(text: 'http://www.jdb-labs.com/timestamper',
constraints: 'growx', foreground: [0,0,200], editable: false,
horizontalAlignment: SwingConstants.CENTER)
}
}

165
gui/griffonw Normal file
View File

@ -0,0 +1,165 @@
#!/bin/bash
##############################################################################
##
## Griffon start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRIFFON_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Griffon"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/"
APP_HOME="`pwd -P`"
cd "$SAVED"
CLASSPATH=$APP_HOME/wrapper/griffon-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add APP_NAME to the JAVA_OPTS as -Xdock:name
if $darwin; then
JAVA_OPTS="$JAVA_OPTS -Xdock:name=$APP_NAME"
# we may also want to set -Xdock:image
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRIFFON_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRIFFON_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRIFFON_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRIFFON_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GriffonWrapperMain "$@"

90
gui/griffonw.bat Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Griffon startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRIFFON_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\wrapper\griffon-wrapper.jar
@rem Execute Griffon
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRIFFON_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GriffonWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRIFFON_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRIFFON_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
gui/keystore.jks Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More