40 files changed, 1878 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed613b6 --- a/dev/null +++ b/.gitignore | |||
@@ -0,0 +1,14 @@ | |||
1 | /INSTALL | ||
2 | Makefile | ||
3 | Makefile.in | ||
4 | /aclocal.m4 | ||
5 | /autom4te.cache | ||
6 | /aux.d | ||
7 | /config.h | ||
8 | /config.h.in | ||
9 | /config.log | ||
10 | /config.status | ||
11 | /configure | ||
12 | /libtool | ||
13 | /stamp-h1 | ||
14 | /NEWS | ||
@@ -0,0 +1,8 @@ | |||
1 | Klever dissected: | ||
2 | Michael 'hacker' Krelin <hacker@klever.net> | ||
3 | Leonid Ivanov <kamel@klever.net> | ||
4 | |||
5 | The credits also go to the unknown hacker called Joe for dissecting | ||
6 | sleeptracker data - | ||
7 | http://www.sleeptracker-tec.de/tools/phorum/read.php?2,19 | ||
8 | I hope I find the way to contact him and give him proper credit. | ||
@@ -0,0 +1,19 @@ | |||
1 | Copyright (c) 2008 Klever Group (http://www.klever.net/) | ||
2 | |||
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
4 | this software and associated documentation files (the "Software"), to deal in | ||
5 | the Software without restriction, including without limitation the rights to | ||
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
7 | of the Software, and to permit persons to whom the Software is furnished to do | ||
8 | so, subject to the following conditions: | ||
9 | |||
10 | The above copyright notice and this permission notice shall be included in all | ||
11 | copies or substantial portions of the Software. | ||
12 | |||
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
19 | SOFTWARE. | ||
diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- a/dev/null +++ b/ChangeLog | |||
diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..c3bc31f --- a/dev/null +++ b/Makefile.am | |||
@@ -0,0 +1,15 @@ | |||
1 | SUBDIRS=include lib src test | ||
2 | EXTRA_DIST = NEWS NEWS.xml NEWS.xsl | ||
3 | |||
4 | all-local: NEWS | ||
5 | |||
6 | NEWS: NEWS.xsl NEWS.xml | ||
7 | ${XSLTPROC} -o $@ NEWS.xsl NEWS.xml | ||
8 | |||
9 | ISSUEFILES = $$(find ${top_srcdir} -type f '(' \ | ||
10 | -name '*.cc' -or -name '*.h' -or -name '*.sql' \ | ||
11 | ')' ) \ | ||
12 | ${top_srcdir}/configure.ac | ||
13 | issues: todo fixme xxx | ||
14 | todo fixme xxx: | ||
15 | @grep --color=auto -in '$@:' ${ISSUEFILES} || true | ||
diff --git a/NEWS.xml b/NEWS.xml new file mode 100644 index 0000000..33907ee --- a/dev/null +++ b/NEWS.xml | |||
@@ -0,0 +1,6 @@ | |||
1 | <?xml version="1.0" encoding="us-ascii"?> | ||
2 | <news> | ||
3 | <version version="0.0" date="April 5th, 2008"> | ||
4 | <ni>Initial release</ni> | ||
5 | </version> | ||
6 | </news> | ||
diff --git a/NEWS.xsl b/NEWS.xsl new file mode 100644 index 0000000..7c71307 --- a/dev/null +++ b/NEWS.xsl | |||
@@ -0,0 +1,24 @@ | |||
1 | <?xml version="1.0" encoding="us-ascii"?> | ||
2 | <xsl:stylesheet version="1.0" | ||
3 | xmlns:xsl="http://www.w3.org/1999/XSL/Transform" | ||
4 | > | ||
5 | <xsl:output | ||
6 | method="text" | ||
7 | encoding="us-ascii" | ||
8 | media-type="text/plain" /> | ||
9 | |||
10 | <xsl:template match="news"> | ||
11 | <xsl:apply-templates/> | ||
12 | </xsl:template> | ||
13 | <xsl:template match="version"> | ||
14 | <xsl:value-of select="concat(@version,' (',@date,')
')"/> | ||
15 | <xsl:apply-templates/> | ||
16 | </xsl:template> | ||
17 | <xsl:template match="ni"> | ||
18 | <xsl:text> - </xsl:text> | ||
19 | <xsl:apply-templates mode="text"/> | ||
20 | <xsl:text>
</xsl:text> | ||
21 | </xsl:template> | ||
22 | <xsl:template match="*|text()"/> | ||
23 | |||
24 | </xsl:stylesheet> | ||
@@ -0,0 +1,15 @@ | |||
1 | |||
2 | Napkin expects you to have sleeptracker port at /dev/sleeptracker | ||
3 | |||
4 | On linux you'd need to have 'ftdi_sio' kernel module loaded (and usb support, | ||
5 | of course) and something along these lines in your udev rules: | ||
6 | |||
7 | BUS=="usb", SYSFS{product}=="FT232R USB UART", SYSFS{serial}=="********", SYSFS{manufacturer}=="FTDI", KERNEL=="ttyUSB*", SYMLINK="sleeptracker", MODE="660", GROUP="usb" | ||
8 | |||
9 | Note, that the I've wiped out the serial number, so that you don't try to use | ||
10 | mine. Perhaps it will differ, although I have only one device. | ||
11 | |||
12 | You can also set the other port name using SLEEPTRACKER_PORT environment | ||
13 | variable, like | ||
14 | |||
15 | env SLEEPTRACKER_PORT=/dev/ttyUSB0 napkin | ||
diff --git a/autogen.sh b/autogen.sh new file mode 100644 index 0000000..c3313be --- a/dev/null +++ b/autogen.sh | |||
@@ -0,0 +1,7 @@ | |||
1 | #!/bin/sh | ||
2 | test -d aux.d || mkdir aux.d | ||
3 | aclocal \ | ||
4 | && autoheader \ | ||
5 | && automake -a \ | ||
6 | && autoconf \ | ||
7 | && ./configure "$@" | ||
diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..3ca0b4e --- a/dev/null +++ b/configure.ac | |||
@@ -0,0 +1,66 @@ | |||
1 | AC_INIT([napkin], [0.0], [napkin-bugs@klever.net]) | ||
2 | AC_CONFIG_AUX_DIR([aux.d]) | ||
3 | AC_CONFIG_SRCDIR([src/napkin.cc]) | ||
4 | AC_CONFIG_HEADERS([config.h]) | ||
5 | AM_INIT_AUTOMAKE([dist-bzip2]) | ||
6 | |||
7 | AC_PROG_INSTALL | ||
8 | AC_PROG_CXX | ||
9 | AC_PROG_CC | ||
10 | AC_PROG_LIBTOOL | ||
11 | PKG_PROG_PKG_CONFIG | ||
12 | |||
13 | AC_HEADER_STDC | ||
14 | |||
15 | AC_PATH_PROG([XSLTPROC],[xsltproc],[true]) | ||
16 | |||
17 | PKG_CHECK_MODULES([MODULES],[gtkmm-2.4 sqlite3],,[ | ||
18 | AC_MSG_ERROR([not all dependencies could be satisfied]) | ||
19 | ]) | ||
20 | |||
21 | AC_MSG_CHECKING([whether to enable debugging code]) | ||
22 | ndebug=true | ||
23 | AC_ARG_ENABLE([debug], | ||
24 | AC_HELP_STRING([--enable-debug],[enable debugging/development code]), | ||
25 | [ test "$enableval" = "no" || ndebug=false ] | ||
26 | ) | ||
27 | if $ndebug ; then | ||
28 | AC_MSG_RESULT([no]) | ||
29 | CPPFLAGS="${CPPFLAGS}-DNDEBUG" | ||
30 | else | ||
31 | AC_MSG_RESULT([yes]) | ||
32 | fi | ||
33 | |||
34 | nitpick=false | ||
35 | AC_MSG_CHECKING([whether to enable compiler nitpicking]) | ||
36 | AC_ARG_ENABLE([nitpicking], | ||
37 | AC_HELP_STRING([--enable-nitpicking],[make compiler somewhat overly fastidious about the code it deals with]), | ||
38 | [ test "$enableval" = "no" || nitpick=true ] | ||
39 | ) | ||
40 | if $nitpick ; then | ||
41 | AC_MSG_RESULT([yes]) | ||
42 | CPP_NITPICK="-pedantic -Wall -Wextra -Wundef -Wshadow \ | ||
43 | -Wunsafe-loop-optimizations -Wconversion -Wmissing-format-attribute \ | ||
44 | -Wredundant-decls -ansi" | ||
45 | # -Wlogical-op -Wmissing-noreturn | ||
46 | C_NITPICK="$CPP_NITPICK" | ||
47 | CXX_NITPICK="$C_NITPICK" | ||
48 | |||
49 | CPPFLAGS="$CPPFLAGS $CPP_NITPICK" | ||
50 | CFLAGS="$CFLAGS $C_NITPICK" | ||
51 | CXXFLAGS="$CXXFLAGS $CXX_NITPICK" | ||
52 | else | ||
53 | AC_MSG_RESULT([no]) | ||
54 | fi | ||
55 | |||
56 | |||
57 | AC_CONFIG_FILES([ | ||
58 | Makefile | ||
59 | include/Makefile | ||
60 | lib/Makefile | ||
61 | src/Makefile | ||
62 | test/Makefile | ||
63 | ]) | ||
64 | AC_OUTPUT | ||
65 | |||
66 | dnl vim:set ft=m4 sw=1: | ||
diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..f37e4d7 --- a/dev/null +++ b/include/Makefile.am | |||
@@ -0,0 +1,4 @@ | |||
1 | include_HEADERS = $(addprefix napkin/,\ | ||
2 | exception.h types.h util.h \ | ||
3 | st/decode.h st/download.h \ | ||
4 | ) | ||
diff --git a/include/napkin/exception.h b/include/napkin/exception.h new file mode 100644 index 0000000..b317886 --- a/dev/null +++ b/include/napkin/exception.h | |||
@@ -0,0 +1,36 @@ | |||
1 | #ifndef __NAPKIN_EXCEPTION_H | ||
2 | #define __NAPKIN_EXCEPTION_H | ||
3 | |||
4 | #include <stdexcept> | ||
5 | #include <string> | ||
6 | |||
7 | #defineNAPKIN_E_SUBCLASS(derived,base) \ | ||
8 | class derived : public base { \ | ||
9 | public: \ | ||
10 | explicit derived(const string& w) \ | ||
11 | : base(w) { } \ | ||
12 | } | ||
13 | |||
14 | namespace napkin { | ||
15 | using std::string; | ||
16 | |||
17 | class exception : public std::runtime_error { | ||
18 | public: | ||
19 | explicit exception(const string& w) | ||
20 | : std::runtime_error(w) { } | ||
21 | ~exception() throw() { } | ||
22 | }; | ||
23 | |||
24 | NAPKIN_E_SUBCLASS(exception_sleeptracker,exception); | ||
25 | NAPKIN_E_SUBCLASS(exception_st_port,exception_sleeptracker); | ||
26 | NAPKIN_E_SUBCLASS(exception_st_data,exception_sleeptracker); | ||
27 | NAPKIN_E_SUBCLASS(exception_st_data_envelope,exception_st_data); | ||
28 | NAPKIN_E_SUBCLASS(exception_st_data_integrity,exception_st_data_envelope); | ||
29 | |||
30 | NAPKIN_E_SUBCLASS(exception_db,exception); | ||
31 | NAPKIN_E_SUBCLASS(exception_db_already,exception_db); | ||
32 | } | ||
33 | |||
34 | #undef NAPKIN_E_SUBCLASS | ||
35 | |||
36 | #endif /* __NAPKIN_EXCEPTION_H */ | ||
diff --git a/include/napkin/st/decode.h b/include/napkin/st/decode.h new file mode 100644 index 0000000..e1f1d07 --- a/dev/null +++ b/include/napkin/st/decode.h | |||
@@ -0,0 +1,15 @@ | |||
1 | #ifndef __NAPKIN_ST_DECODE_H | ||
2 | #define __NAPKIN_ST_DECODE_H | ||
3 | |||
4 | #include <napkin/types.h> | ||
5 | |||
6 | namespace napkin { | ||
7 | namespace sleeptracker { | ||
8 | |||
9 | hypnodata_t& decode(hypnodata_t& rv,const void *data,size_t data_length); | ||
10 | hypnodata_ptr_t decode(const void *data,size_t data_length); | ||
11 | |||
12 | } | ||
13 | } | ||
14 | |||
15 | #endif /* __NAPKIN_ST_DECODE_H */ | ||
diff --git a/include/napkin/st/download.h b/include/napkin/st/download.h new file mode 100644 index 0000000..92d1d9d --- a/dev/null +++ b/include/napkin/st/download.h | |||
@@ -0,0 +1,20 @@ | |||
1 | #ifndef __NAPKIN_ST_DOWNLOAD_H | ||
2 | #define __NAPKIN_ST_DOWNLOAD_H | ||
3 | |||
4 | #include <napkin/types.h> | ||
5 | |||
6 | namespace napkin { | ||
7 | namespace sleeptracker { | ||
8 | |||
9 | int download_initiate(const char *port=0); | ||
10 | size_t download_finish(int fd,void *buffer,size_t buffer_size); | ||
11 | |||
12 | size_t download( | ||
13 | void *buffer,size_t buffer_size, | ||
14 | const char *port=0); | ||
15 | hypnodata_ptr_t download(const char *port=0); | ||
16 | |||
17 | } | ||
18 | } | ||
19 | |||
20 | #endif /* __NAPKIN_ST_DOWNLOAD_H */ | ||
diff --git a/include/napkin/types.h b/include/napkin/types.h new file mode 100644 index 0000000..2bc3a0a --- a/dev/null +++ b/include/napkin/types.h | |||
@@ -0,0 +1,46 @@ | |||
1 | #ifndef __NAPKIN_TYPES_H | ||
2 | #define __NAPKIN_TYPES_H | ||
3 | |||
4 | #include <time.h> | ||
5 | #include <string> | ||
6 | #include <vector> | ||
7 | #include <tr1/memory> | ||
8 | |||
9 | namespace napkin { | ||
10 | using std::vector; | ||
11 | using std::tr1::shared_ptr; | ||
12 | using std::string; | ||
13 | |||
14 | class hypnodata_t { | ||
15 | public: | ||
16 | time_t to_bed; | ||
17 | time_t alarm; | ||
18 | int window; | ||
19 | vector<time_t> almost_awakes; | ||
20 | int data_a; | ||
21 | |||
22 | void clear(); | ||
23 | |||
24 | void set_to_bed(const string& w3c); | ||
25 | void set_alarm(const string& w3c); | ||
26 | void set_window(const string& str); | ||
27 | void set_data_a(const string& str); | ||
28 | void set_almost_awakes(const string& str); | ||
29 | |||
30 | const string w3c_to_bed() const; | ||
31 | const string w3c_alarm() const; | ||
32 | const string w3c_almostawakes() const; | ||
33 | |||
34 | const string str_to_bed() const; | ||
35 | const string str_alarm() const; | ||
36 | const string str_date() const; | ||
37 | const string str_data_a() const; | ||
38 | |||
39 | time_t aligned_start() const; | ||
40 | }; | ||
41 | |||
42 | typedef shared_ptr<hypnodata_t> hypnodata_ptr_t; | ||
43 | |||
44 | } | ||
45 | |||
46 | #endif /* __NAPKIN_TYPES_H */ | ||
diff --git a/include/napkin/util.h b/include/napkin/util.h new file mode 100644 index 0000000..bf7946d --- a/dev/null +++ b/include/napkin/util.h | |||
@@ -0,0 +1,14 @@ | |||
1 | #ifndef __NAPKIN_UTIL_H | ||
2 | #define __NAPKIN_UTIL_H | ||
3 | |||
4 | #include <time.h> | ||
5 | #include <string> | ||
6 | |||
7 | namespace napkin { | ||
8 | using std::string; | ||
9 | |||
10 | string strftime(const char *fmt,time_t t); | ||
11 | |||
12 | } | ||
13 | |||
14 | #endif /* __NAPKIN_UTIL_H */ | ||
diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..b7b1941 --- a/dev/null +++ b/lib/.gitignore | |||
@@ -0,0 +1,11 @@ | |||
1 | /.deps | ||
2 | /.libs | ||
3 | /st-decode.lo | ||
4 | /st-decode.o | ||
5 | /libnapkin.la | ||
6 | /st-download.lo | ||
7 | /st-download.o | ||
8 | /hypnodata.lo | ||
9 | /hypnodata.o | ||
10 | /util.lo | ||
11 | /util.o | ||
diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..bf55849 --- a/dev/null +++ b/lib/Makefile.am | |||
@@ -0,0 +1,11 @@ | |||
1 | lib_LTLIBRARIES = libnapkin.la | ||
2 | |||
3 | INCLUDES = -I${top_builddir}/include/ -I${top_srcdir}/include/ \ | ||
4 | ${MODULES_CFLAGS} | ||
5 | LIBS = ${MODULES_CFLAGS} | ||
6 | |||
7 | libnapkin_la_SOURCES = \ | ||
8 | st-decode.cc st-download.cc \ | ||
9 | hypnodata.cc \ | ||
10 | util.cc | ||
11 | libnapkin_la_LDFLAGS = -version-info 0:0:0 | ||
diff --git a/lib/hypnodata.cc b/lib/hypnodata.cc new file mode 100644 index 0000000..977fb76 --- a/dev/null +++ b/lib/hypnodata.cc | |||
@@ -0,0 +1,92 @@ | |||
1 | #include <napkin/exception.h> | ||
2 | #include <napkin/util.h> | ||
3 | #include <napkin/types.h> | ||
4 | |||
5 | namespace napkin { | ||
6 | |||
7 | void hypnodata_t::clear() { | ||
8 | to_bed = alarm = 0; | ||
9 | data_a = window = 0; | ||
10 | almost_awakes.clear(); | ||
11 | } | ||
12 | |||
13 | static time_t from_minute_w3c(const string& w3c) { | ||
14 | struct tm t; memset(&t,0,sizeof(t)); t.tm_isdst=-1; | ||
15 | if(sscanf(w3c.c_str(),"%04d-%02d-%02dT%02d:%02d", | ||
16 | &t.tm_year,&t.tm_mon,&t.tm_mday,&t.tm_hour,&t.tm_min)!=5) | ||
17 | throw exception("failed to parse w3c time"); | ||
18 | --t.tm_mon;t.tm_year-=1900; | ||
19 | time_t rv = mktime(&t); | ||
20 | if(rv==(time_t)-1) | ||
21 | throw exception("failed to mktime()"); | ||
22 | return rv; | ||
23 | } | ||
24 | |||
25 | void hypnodata_t::set_to_bed(const string& w3c) { | ||
26 | to_bed = from_minute_w3c(w3c); } | ||
27 | void hypnodata_t::set_alarm(const string& w3c) { | ||
28 | alarm = from_minute_w3c(w3c); } | ||
29 | void hypnodata_t::set_window(const string& str) { | ||
30 | window = strtol(str.c_str(),0,10); /* TODO: check for error */ | ||
31 | } | ||
32 | void hypnodata_t::set_data_a(const string& str) { | ||
33 | data_a = strtol(str.c_str(),0,10); /* TODO: check for error */ | ||
34 | } | ||
35 | void hypnodata_t::set_almost_awakes(const string& str) { | ||
36 | almost_awakes.clear(); | ||
37 | static const char *significants = "0123456789-T:Z"; | ||
38 | string::size_type p = str.find_first_of(significants); | ||
39 | struct tm t; memset(&t,0,sizeof(t)); t.tm_isdst=-1; | ||
40 | while(p!=string::npos) { | ||
41 | string::size_type ns = str.find_first_not_of(significants,p); | ||
42 | string w3c; | ||
43 | if(ns==string::npos) { | ||
44 | w3c = str.substr(p); | ||
45 | p = string::npos; | ||
46 | }else{ | ||
47 | w3c = str.substr(p,ns-p); | ||
48 | p = str.find_first_of(significants,ns); | ||
49 | } | ||
50 | if(w3c.empty()) continue; | ||
51 | if(sscanf(w3c.c_str(),"%04d-%02d-%02dT%02d:%02d:%02d", | ||
52 | &t.tm_year,&t.tm_mon,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec)!=6) | ||
53 | throw exception("failed to parse w3c time"); | ||
54 | --t.tm_mon;t.tm_year-=1900; | ||
55 | time_t aa = mktime(&t); | ||
56 | if(aa==(time_t)-1) | ||
57 | throw exception("failed to mktime()"); | ||
58 | almost_awakes.push_back(aa); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | const string hypnodata_t::w3c_to_bed() const { | ||
63 | return strftime("%Y-%m-%dT%H:%M",to_bed); } | ||
64 | const string hypnodata_t::w3c_alarm() const { | ||
65 | return strftime("%Y-%m-%dT%H:%M",alarm); } | ||
66 | const string hypnodata_t::w3c_almostawakes() const { | ||
67 | string rv; | ||
68 | for(vector<time_t>::const_iterator i=almost_awakes.begin();i!=almost_awakes.end();++i) { | ||
69 | if(!rv.empty()) | ||
70 | rv += ','; | ||
71 | rv += strftime("%Y-%m-%dT%H:%M:%S",*i); | ||
72 | } | ||
73 | return rv; | ||
74 | } | ||
75 | |||
76 | const string hypnodata_t::str_to_bed() const { | ||
77 | return strftime("%H:%M",to_bed); } | ||
78 | const string hypnodata_t::str_alarm() const { | ||
79 | return strftime("%H:%M",alarm); } | ||
80 | const string hypnodata_t::str_date() const { | ||
81 | return strftime("%Y-%m-%d, %a",alarm); } | ||
82 | const string hypnodata_t::str_data_a() const { | ||
83 | char tmp[16]; | ||
84 | snprintf(tmp,sizeof(tmp),"%d:%02d:%02d", | ||
85 | data_a/3600, (data_a%3600)/60, | ||
86 | data_a % 60 ); | ||
87 | return tmp; } | ||
88 | |||
89 | time_t hypnodata_t::aligned_start() const { | ||
90 | return alarm - (alarm % (24*60*60)) - 24*60*60; } | ||
91 | |||
92 | } | ||
diff --git a/lib/st-decode.cc b/lib/st-decode.cc new file mode 100644 index 0000000..f8459ac --- a/dev/null +++ b/lib/st-decode.cc | |||
@@ -0,0 +1,121 @@ | |||
1 | #include <stdexcept> | ||
2 | #include <numeric> | ||
3 | #include <napkin/exception.h> | ||
4 | #include <napkin/st/decode.h> | ||
5 | |||
6 | namespace napkin { | ||
7 | namespace sleeptracker { | ||
8 | using std::invalid_argument; | ||
9 | using std::runtime_error; | ||
10 | |||
11 | struct st_time_t { | ||
12 | uint8_t hour; | ||
13 | uint8_t min; | ||
14 | }; | ||
15 | struct st_date_t { | ||
16 | uint8_t month; | ||
17 | uint8_t day; | ||
18 | }; | ||
19 | struct st_fulltime_t { | ||
20 | uint8_t hour; | ||
21 | uint8_t min; | ||
22 | uint8_t sec; | ||
23 | }; | ||
24 | struct st_data_header_t { | ||
25 | char magic; | ||
26 | st_date_t today; | ||
27 | uint8_t unknown; | ||
28 | uint8_t window; | ||
29 | st_time_t to_bed; | ||
30 | st_time_t alarm; | ||
31 | uint8_t nawakes; | ||
32 | }; | ||
33 | struct st_data_footer_t { | ||
34 | uint16_t data_a; | ||
35 | uint8_t checksum; | ||
36 | uint8_t eof_mark; | ||
37 | }; | ||
38 | |||
39 | static void back_a_day(struct tm& t) { | ||
40 | time_t ts = mktime(&t); | ||
41 | if(ts==(time_t)-1) | ||
42 | throw exception_st_data("failed to make up time to step back a day"); | ||
43 | ts -= 60*60*24; | ||
44 | if(!localtime_r(&ts,&t)) | ||
45 | throw exception_st_data("failed to localtime_r() while stepping back a day"); | ||
46 | } | ||
47 | |||
48 | hypnodata_t& decode(hypnodata_t& rv,const void *data,size_t data_length) { | ||
49 | if(data_length < (sizeof(st_data_header_t)+sizeof(st_data_footer_t))) | ||
50 | throw exception_st_data_envelope("not enough sleeptracker data to decode"); | ||
51 | st_data_header_t *h = (st_data_header_t*)data; | ||
52 | if(h->magic != 'V') | ||
53 | throw exception_st_data_envelope("invalid magic in the data"); | ||
54 | st_data_footer_t *f = (st_data_footer_t*)(static_cast<const char *>(data)+data_length-sizeof(st_data_footer_t)); | ||
55 | if( (std::accumulate((uint8_t*)&h->today,(uint8_t*)&f->checksum,0)&0xFF) != f->checksum ) | ||
56 | throw exception_st_data_integrity("checksum mismatch"); | ||
57 | st_fulltime_t *aawake = (st_fulltime_t*)&h[1]; | ||
58 | if((void*)&aawake[h->nawakes] != (void*)f) | ||
59 | throw exception_st_data_envelope("unbelievably screwed up data"); | ||
60 | rv.clear(); | ||
61 | time_t now = time(0); | ||
62 | struct tm t; | ||
63 | if(!localtime_r(&now,&t)) | ||
64 | throw exception_st_data("failed to localtime_r()"); | ||
65 | t.tm_mon = h->today.month-1; | ||
66 | t.tm_mday = h->today.day; | ||
67 | time_t mkt = mktime(&t); | ||
68 | if(mkt == (time_t)-1) | ||
69 | throw exception_st_data("failed to mktime() for a timestamp"); | ||
70 | if(mkt > now) { | ||
71 | --t.tm_year; | ||
72 | } | ||
73 | struct tm ta; | ||
74 | memmove(&ta,&t,sizeof(ta)); | ||
75 | ta.tm_sec = 0; | ||
76 | ta.tm_hour = h->alarm.hour; ta.tm_min = h->alarm.min; | ||
77 | rv.alarm = mktime(&ta); | ||
78 | if(rv.alarm == (time_t)-1) | ||
79 | throw exception_st_data("failed to mktime() for alarm"); | ||
80 | struct tm tb; | ||
81 | memmove(&tb,&ta,sizeof(tb)); | ||
82 | tb.tm_hour = h->to_bed.hour; tb.tm_min = h->to_bed.min; | ||
83 | rv.to_bed = mktime(&tb); | ||
84 | if(rv.to_bed == (time_t)-1) | ||
85 | throw exception_st_data("failed to mktime() for 'to bed'"); | ||
86 | if(rv.to_bed > rv.alarm) { | ||
87 | back_a_day(tb); | ||
88 | rv.to_bed -= 24*60*60; | ||
89 | } | ||
90 | struct tm taaw; | ||
91 | memmove(&taaw,&tb,sizeof(taaw)); | ||
92 | for(int rest=h->nawakes;rest;--rest,++aawake) { | ||
93 | if( | ||
94 | taaw.tm_mday!=ta.tm_mday | ||
95 | && ( | ||
96 | aawake->hour < tb.tm_hour | ||
97 | || ( | ||
98 | aawake->hour==tb.tm_hour | ||
99 | && aawake->min < tb.tm_min ) | ||
100 | ) ) | ||
101 | memmove(&taaw,&ta,sizeof(taaw)); | ||
102 | taaw.tm_hour = aawake->hour; | ||
103 | taaw.tm_min = aawake->min; | ||
104 | taaw.tm_sec = aawake->sec; | ||
105 | rv.almost_awakes.push_back( mktime(&taaw) ); | ||
106 | if(rv.almost_awakes.back() == (time_t)-1) | ||
107 | throw exception_st_data("failed to mktime() for almost awake moment"); | ||
108 | } | ||
109 | rv.window = h->window; | ||
110 | rv.data_a = f->data_a; | ||
111 | return rv; | ||
112 | } | ||
113 | |||
114 | hypnodata_ptr_t decode(const void *data,size_t data_length) { | ||
115 | hypnodata_ptr_t rv( new hypnodata_t ); | ||
116 | decode(*rv,data,data_length); | ||
117 | return rv; | ||
118 | } | ||
119 | |||
120 | } | ||
121 | } | ||
diff --git a/lib/st-download.cc b/lib/st-download.cc new file mode 100644 index 0000000..b56e52d --- a/dev/null +++ b/lib/st-download.cc | |||
@@ -0,0 +1,70 @@ | |||
1 | #include <sys/types.h> | ||
2 | #include <sys/stat.h> | ||
3 | #include <fcntl.h> | ||
4 | #include <unistd.h> | ||
5 | #include <termios.h> | ||
6 | #include <stdexcept> | ||
7 | #include <napkin/exception.h> | ||
8 | #include <napkin/st/download.h> | ||
9 | #include <napkin/st/decode.h> | ||
10 | |||
11 | namespace napkin { | ||
12 | namespace sleeptracker { | ||
13 | using std::runtime_error; | ||
14 | |||
15 | intdownload_initiate(const char *port) { | ||
16 | int fd = open(port?port:"/dev/sleeptracker", | ||
17 | O_RDWR|O_NOCTTY|O_NONBLOCK); | ||
18 | if(fd<0) | ||
19 | throw exception_st_port("failed to open() sleeptracker port"); | ||
20 | |||
21 | if(tcflush(fd,TCIOFLUSH)) { | ||
22 | close(fd); | ||
23 | throw exception_st_port("failed to tcflush()"); | ||
24 | } | ||
25 | struct termios ts; | ||
26 | ts.c_cflag = CS8|CREAD; | ||
27 | cfsetispeed(&ts,B2400); cfsetospeed(&ts,B2400); | ||
28 | ts.c_iflag = IGNPAR; | ||
29 | ts.c_oflag = ts.c_lflag = 0; | ||
30 | ts.c_cc[VMIN]=1; ts.c_cc[VTIME]=0; | ||
31 | if(tcsetattr(fd,TCSANOW,&ts)) { | ||
32 | close(fd); | ||
33 | throw exception_st_port("failed to tcsetattr()"); | ||
34 | } | ||
35 | |||
36 | if(write(fd,"V",1)!=1) { | ||
37 | close(fd); | ||
38 | throw exception_st_port("failed to write() to sleeptracker"); | ||
39 | } | ||
40 | return fd; | ||
41 | } | ||
42 | size_t download_finish(int fd,void *buffer,size_t buffer_size) { | ||
43 | size_t rv = read(fd,buffer,buffer_size); | ||
44 | close(fd); | ||
45 | |||
46 | if(rv==(size_t)-1) | ||
47 | throw exception_st_port("failed to read() from sleeptracker"); | ||
48 | return rv; | ||
49 | } | ||
50 | |||
51 | size_t download( | ||
52 | void *buffer,size_t buffer_size, | ||
53 | const char *port) { | ||
54 | int fd = download_initiate(port); | ||
55 | /* this is not the best way to wait for data, but | ||
56 | * after all it's a sleeptracker! */ | ||
57 | sleep(1); | ||
58 | return download_finish(fd,buffer,buffer_size); | ||
59 | } | ||
60 | |||
61 | hypnodata_ptr_t download(const char *port) { | ||
62 | char buffer[2048]; | ||
63 | size_t rb = download(buffer,sizeof(buffer),port); | ||
64 | hypnodata_ptr_t rv( new hypnodata_t ); | ||
65 | decode(*rv,buffer,rb); | ||
66 | return rv; | ||
67 | } | ||
68 | |||
69 | } | ||
70 | } | ||
diff --git a/lib/util.cc b/lib/util.cc new file mode 100644 index 0000000..350cbac --- a/dev/null +++ b/lib/util.cc | |||
@@ -0,0 +1,13 @@ | |||
1 | #include <napkin/util.h> | ||
2 | |||
3 | namespace napkin { | ||
4 | |||
5 | string strftime(const char *fmt,time_t t) { | ||
6 | struct tm tt; | ||
7 | localtime_r(&t,&tt);// TODO: check res | ||
8 | char rv[1024]; | ||
9 | strftime(rv,sizeof(rv),fmt,&tt); // TODO: check res | ||
10 | return rv; | ||
11 | } | ||
12 | |||
13 | } | ||
diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..1210d2d --- a/dev/null +++ b/src/.gitignore | |||
@@ -0,0 +1,13 @@ | |||
1 | /napkin | ||
2 | /napkin.o | ||
3 | /.deps | ||
4 | /.libs | ||
5 | /schema.cc | ||
6 | /schema.o | ||
7 | /db.o | ||
8 | /dialogs.o | ||
9 | /sleep_history.o | ||
10 | /sleep_timeline.o | ||
11 | /widgets.o | ||
12 | /COPYING.cc | ||
13 | /COPYING.o | ||
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..1fdae31 --- a/dev/null +++ b/src/Makefile.am | |||
@@ -0,0 +1,32 @@ | |||
1 | bin_PROGRAMS = napkin | ||
2 | |||
3 | AM_CXXFLAGS = ${MODULES_CFLAGS} -I${top_srcdir}/include/ -I${srcdir} | ||
4 | LIBS = ${MODULES_LIBS} \ | ||
5 | ${top_builddir}/lib/libnapkin.la | ||
6 | |||
7 | noinst_HEADERS = sqlite.h db.h \ | ||
8 | sleep_timeline.h \ | ||
9 | widgets.h dialogs.h \ | ||
10 | sleep_history.h | ||
11 | |||
12 | napkin_SOURCES = napkin.cc \ | ||
13 | db.cc \ | ||
14 | sleep_timeline.cc \ | ||
15 | widgets.cc dialogs.cc \ | ||
16 | sleep_history.cc \ | ||
17 | schema.cc COPYING.cc | ||
18 | napkin_DEPENDENCIES = \ | ||
19 | ${top_builddir}/lib/libnapkin.la | ||
20 | |||
21 | EXTRA_DIST = schema.sql | ||
22 | |||
23 | schema.cc: schema.sql | ||
24 | (\ | ||
25 | echo 'namespace napkin{const char *sql_bootstrap=' &&\ | ||
26 | sed -e 's/^\s\+//' -e 's/\s*--.*$$//' -e 's/^/"/' -e 's/$$/"/' $< &&\ | ||
27 | echo ';}'\ | ||
28 | ) >$@ | ||
29 | COPYING.cc: ${top_srcdir}/COPYING | ||
30 | echo "const char * COPYING =" >$@ || (rm $@;exit 1) | ||
31 | sed 's/"/\\"/g' $< | sed 's/^/\"/' | sed 's/$$/\\n\"/' >>$@ || (rm $@;exit 1) | ||
32 | echo ";" >>$@ || (rm $@;exit 1) | ||
diff --git a/src/db.cc b/src/db.cc new file mode 100644 index 0000000..d1e0a85 --- a/dev/null +++ b/src/db.cc | |||
@@ -0,0 +1,101 @@ | |||
1 | #include <unistd.h> | ||
2 | #include <sys/stat.h> | ||
3 | #include <sys/types.h> | ||
4 | #include <cassert> | ||
5 | #include <napkin/exception.h> | ||
6 | #include "db.h" | ||
7 | |||
8 | #include "config.h" | ||
9 | |||
10 | namespace napkin { | ||
11 | |||
12 | extern const char *sql_bootstrap; | ||
13 | |||
14 | db_t::db_t() { | ||
15 | const char *h = getenv("HOME"); | ||
16 | if(h) { | ||
17 | datadir = h; | ||
18 | datadir += "/."PACKAGE_NAME"/"; | ||
19 | }else{ | ||
20 | char *cwd = get_current_dir_name(); | ||
21 | if(!cwd) | ||
22 | throw napkin::exception("failed to get_current_dir_name()"); | ||
23 | datadir = cwd; | ||
24 | free(cwd); | ||
25 | datadir += "/."PACKAGE_NAME"/"; | ||
26 | } | ||
27 | if(access(datadir.c_str(),R_OK|W_OK) | ||
28 | && mkdir(datadir.c_str(),0700)) | ||
29 | throw napkin::exception("no access to '"+datadir+"' directory"); | ||
30 | open((datadir+PACKAGE_NAME".db").c_str()); | ||
31 | assert(_D); | ||
32 | char **resp; int nr,nc; char *errm; | ||
33 | if(sqlite3_get_table( | ||
34 | _D, | ||
35 | "SELECT s_tobed FROM sleeps LIMIT 0", | ||
36 | &resp,&nr,&nc,&errm)!=SQLITE_OK) { | ||
37 | if(sqlite3_exec(_D,sql_bootstrap,NULL,NULL,&errm)!=SQLITE_OK) | ||
38 | throw napkin::exception(string("failed to bootstrap sqlite database: ")+errm); | ||
39 | }else | ||
40 | sqlite3_free_table(resp); | ||
41 | } | ||
42 | |||
43 | void db_t::store(const hypnodata_t& hd) { | ||
44 | sqlite::mem_t<char*> S = sqlite3_mprintf( | ||
45 | "INSERT INTO sleeps (" | ||
46 | "s_tobed,s_alarm," | ||
47 | "s_window,s_data_a," | ||
48 | "s_almost_awakes," | ||
49 | "s_timezone" | ||
50 | ") VALUES (" | ||
51 | "%Q,%Q,%d,%d,%Q,%ld" | ||
52 | ")", | ||
53 | hd.w3c_to_bed().c_str(), | ||
54 | hd.w3c_alarm().c_str(), | ||
55 | hd.window,hd.data_a, | ||
56 | hd.w3c_almostawakes().c_str(), | ||
57 | timezone ); | ||
58 | try { | ||
59 | exec(S); | ||
60 | }catch(sqlite::exception& se) { | ||
61 | if(se.rcode==SQLITE_CONSTRAINT) | ||
62 | throw exception_db_already("The record seems to be already in the database"); | ||
63 | throw exception_db("Well, some error occured"); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | void db_t::remove(const hypnodata_t& hd) { | ||
68 | sqlite::mem_t<char*> S = sqlite3_mprintf( | ||
69 | "DELETE FROM sleeps" | ||
70 | " WHERE s_tobed=%Q AND s_alarm=%Q", | ||
71 | hd.w3c_to_bed().c_str(), | ||
72 | hd.w3c_alarm().c_str() ); | ||
73 | exec(S); | ||
74 | } | ||
75 | |||
76 | void db_t::load(list<hypnodata_ptr_t>& rv, | ||
77 | const string& sql) { | ||
78 | sqlite::table_t T; | ||
79 | int nr,nc; | ||
80 | get_table( string( | ||
81 | "SELECT" | ||
82 | " s_tobed, s_alarm," | ||
83 | " s_window, s_data_a," | ||
84 | " s_almost_awakes" | ||
85 | " FROM sleeps" | ||
86 | " "+sql).c_str(),T,&nr,&nc ); | ||
87 | if(nr) { | ||
88 | assert(nc==5); | ||
89 | for(int r=1;r<=nr;++r) { | ||
90 | hypnodata_ptr_t hd(new hypnodata_t()); | ||
91 | hd->set_to_bed(T.get(r,0,nc)); | ||
92 | hd->set_alarm(T.get(r,1,nc)); | ||
93 | hd->set_window(T.get(r,2,nc)); | ||
94 | hd->set_data_a(T.get(r,3,nc)); | ||
95 | hd->set_almost_awakes(T.get(r,4,nc)); | ||
96 | rv.push_back(hd); | ||
97 | } | ||
98 | } | ||
99 | } | ||
100 | |||
101 | } | ||
diff --git a/src/db.h b/src/db.h new file mode 100644 index 0000000..3794eae --- a/dev/null +++ b/src/db.h | |||
@@ -0,0 +1,28 @@ | |||
1 | #ifndef __N_DB_H | ||
2 | #define __N_DB_H | ||
3 | |||
4 | #include <string> | ||
5 | #include <list> | ||
6 | #include <napkin/types.h> | ||
7 | #include "sqlite.h" | ||
8 | |||
9 | namespace napkin { | ||
10 | using std::string; | ||
11 | using std::list; | ||
12 | |||
13 | class db_t : public sqlite::db_t { | ||
14 | public: | ||
15 | string datadir; | ||
16 | |||
17 | db_t(); | ||
18 | |||
19 | void store(const hypnodata_t& hd); | ||
20 | void remove(const hypnodata_t& hd); | ||
21 | |||
22 | void load(list<hypnodata_ptr_t>& rv, | ||
23 | const string& sql); | ||
24 | }; | ||
25 | |||
26 | } | ||
27 | |||
28 | #endif /* __N_DB_H */ | ||
diff --git a/src/dialogs.cc b/src/dialogs.cc new file mode 100644 index 0000000..efe0b36 --- a/dev/null +++ b/src/dialogs.cc | |||
@@ -0,0 +1,17 @@ | |||
1 | #include "dialogs.h" | ||
2 | |||
3 | namespace napkin { | ||
4 | namespace gtk { | ||
5 | |||
6 | hypnoinfo_dialog_t::hypnoinfo_dialog_t(Gtk::Window& w) | ||
7 | : Gtk::Dialog("Sleepy information",w,true/*modal*/,true/*use separator*/) | ||
8 | { | ||
9 | Gtk::VBox *vb = get_vbox(); | ||
10 | vb->set_spacing(2); | ||
11 | vb->add(w_hinfo); | ||
12 | vb->show_all(); | ||
13 | } | ||
14 | |||
15 | |||
16 | } | ||
17 | } | ||
diff --git a/src/dialogs.h b/src/dialogs.h new file mode 100644 index 0000000..0a7f1b0 --- a/dev/null +++ b/src/dialogs.h | |||
@@ -0,0 +1,25 @@ | |||
1 | #ifndef __N_DIALOGS_H | ||
2 | #define __N_DIALOGS_H | ||
3 | |||
4 | #include <gtkmm/dialog.h> | ||
5 | #include <gtkmm/box.h> | ||
6 | #include "widgets.h" | ||
7 | |||
8 | namespace napkin { | ||
9 | namespace gtk { | ||
10 | |||
11 | class hypnoinfo_dialog_t : public Gtk::Dialog { | ||
12 | public: | ||
13 | hypnoinfo_t w_hinfo; | ||
14 | |||
15 | hypnoinfo_dialog_t(Gtk::Window& w); | ||
16 | |||
17 | inline void update_data(const hypnodata_ptr_t& hd) { | ||
18 | w_hinfo.update_data(hd); } | ||
19 | }; | ||
20 | |||
21 | |||
22 | } | ||
23 | } | ||
24 | |||
25 | #endif /* __N_DIALOGS_H */ | ||
diff --git a/src/napkin.cc b/src/napkin.cc new file mode 100644 index 0000000..d9ba0c9 --- a/dev/null +++ b/src/napkin.cc | |||
@@ -0,0 +1,367 @@ | |||
1 | #include <fcntl.h> | ||
2 | #include <iostream> | ||
3 | using std::cerr; | ||
4 | using std::endl; | ||
5 | #include <fstream> | ||
6 | using std::ofstream; | ||
7 | #include <cstdlib> | ||
8 | using std::min; | ||
9 | #include <stdexcept> | ||
10 | using std::runtime_error; | ||
11 | #include <list> | ||
12 | using std::list; | ||
13 | #include <vector> | ||
14 | using std::vector; | ||
15 | #include <string> | ||
16 | using std::string; | ||
17 | #include <gtkmm/main.h> | ||
18 | #include <gtkmm/window.h> | ||
19 | #include <gtkmm/box.h> | ||
20 | #include <gtkmm/statusbar.h> | ||
21 | #include <gtkmm/uimanager.h> | ||
22 | #include <gtkmm/stock.h> | ||
23 | #include <gtkmm/toolbar.h> | ||
24 | #include <gtkmm/filechooserdialog.h> | ||
25 | #include <gtkmm/messagedialog.h> | ||
26 | #include <gtkmm/aboutdialog.h> | ||
27 | #include <napkin/exception.h> | ||
28 | #include <napkin/util.h> | ||
29 | #include <napkin/st/decode.h> | ||
30 | #include <napkin/st/download.h> | ||
31 | |||
32 | #include "db.h" | ||
33 | #include "sleep_timeline.h" | ||
34 | #include "dialogs.h" | ||
35 | #include "sleep_history.h" | ||
36 | |||
37 | #include "config.h" | ||
38 | |||
39 | class napkin_ui : public Gtk::Window { | ||
40 | public: | ||
41 | Gtk::VBox w_outer_box; | ||
42 | Gtk::Statusbar w_status_bar; | ||
43 | napkin::gtk::sleep_history_t w_history; | ||
44 | Glib::RefPtr<Gtk::UIManager> uiman; | ||
45 | Glib::RefPtr<Gtk::ActionGroup> agroup; | ||
46 | napkin::db_t db; | ||
47 | Glib::RefPtr<Gtk::Action> a_remove; | ||
48 | |||
49 | napkin_ui() | ||
50 | : w_history(db) | ||
51 | { | ||
52 | static char *ui_info = | ||
53 | "<ui>" | ||
54 | "<menubar name='menu_bar'>" | ||
55 | "<menu action='menu_sleep'>" | ||
56 | #ifndef NDEBUG | ||
57 | "<menu action='menu_sleep_add'>" | ||
58 | #endif | ||
59 | "<menuitem action='sleep_add_from_sleeptracker'/>" | ||
60 | #ifndef NDEBUG | ||
61 | "<menuitem action='sleep_add_from_datafile'/>" | ||
62 | "</menu>" | ||
63 | #endif | ||
64 | "<menuitem action='sleep_remove'/>" | ||
65 | "<menuitem action='exit'/>" | ||
66 | "</menu>" | ||
67 | "<menu action='menu_help'>" | ||
68 | "<menuitem action='help_about'/>" | ||
69 | "</menu>" | ||
70 | "</menubar>" | ||
71 | "<toolbar action='tool_bar'>" | ||
72 | "<toolitem action='sleep_add_from_sleeptracker'/>" | ||
73 | "<toolitem action='sleep_remove'/>" | ||
74 | "<separator expand='true'/>" | ||
75 | #ifndef NDEBUG | ||
76 | "<toolitem action='debug'/>" | ||
77 | "<separator/>" | ||
78 | #endif | ||
79 | "<toolitem action='exit'/>" | ||
80 | "</toolbar>" | ||
81 | "</ui>"; | ||
82 | agroup = Gtk::ActionGroup::create(); | ||
83 | agroup->add(Gtk::Action::create("menu_sleep","Sleep")); | ||
84 | agroup->add(Gtk::Action::create("menu_sleep_add","Add")); | ||
85 | agroup->add(Gtk::Action::create("sleep_add_from_sleeptracker",Gtk::Stock::CONNECT, | ||
86 | "from sleeptracker","import sleeptracker data from watch"), | ||
87 | Gtk::AccelKey("<Ctrl>d"), | ||
88 | sigc::mem_fun(*this,&napkin_ui::on_sleep_add_from_sleeptracker)); | ||
89 | #ifndef NDEBUG | ||
90 | agroup->add(Gtk::Action::create("sleep_add_from_datafile",Gtk::Stock::CONVERT, | ||
91 | "from data file","import sleeptracker data stored in a file"), | ||
92 | sigc::mem_fun(*this,&napkin_ui::on_sleep_add_from_datafile)); | ||
93 | #endif | ||
94 | agroup->add(a_remove=Gtk::Action::create("sleep_remove",Gtk::Stock::REMOVE, | ||
95 | "Remove","remove highlighted sleep event from the database"), | ||
96 | Gtk::AccelKey("delete"), | ||
97 | sigc::mem_fun(*this,&napkin_ui::on_remove)); | ||
98 | agroup->add(Gtk::Action::create("exit",Gtk::Stock::QUIT,"Exit","Exit "PACKAGE_NAME), | ||
99 | Gtk::AccelKey("<control>w"), | ||
100 | sigc::mem_fun(*this,&napkin_ui::on_quit)); | ||
101 | agroup->add(Gtk::Action::create("menu_help","Help")); | ||
102 | agroup->add(Gtk::Action::create("help_about",Gtk::Stock::ABOUT, | ||
103 | "About","About this program"), | ||
104 | sigc::mem_fun(*this,&napkin_ui::on_help_about)); | ||
105 | #ifndef NDEBUG | ||
106 | agroup->add(Gtk::Action::create("debug",Gtk::Stock::INFO,"Debug","debug action"), | ||
107 | sigc::mem_fun(*this,&napkin_ui::on_debug)); | ||
108 | #endif | ||
109 | uiman = Gtk::UIManager::create(); | ||
110 | uiman->insert_action_group(agroup); | ||
111 | add_accel_group(uiman->get_accel_group()); | ||
112 | uiman->add_ui_from_string(ui_info); | ||
113 | Gtk::Widget * mb = uiman->get_widget("/menu_bar"); | ||
114 | if(mb) | ||
115 | w_outer_box.pack_start(*mb,Gtk::PACK_SHRINK); | ||
116 | Gtk::Widget * tb = uiman->get_widget("/tool_bar"); | ||
117 | if(tb) { | ||
118 | static_cast<Gtk::Toolbar*>(tb)->set_toolbar_style(Gtk::TOOLBAR_ICONS); | ||
119 | w_outer_box.pack_start(*tb,Gtk::PACK_SHRINK); | ||
120 | } | ||
121 | w_outer_box.pack_start(w_history,true/*expand*/,true/*fill*/); | ||
122 | w_outer_box.pack_end(w_status_bar,false/*expand*/,false/*fill*/); | ||
123 | add(w_outer_box); | ||
124 | set_title(PACKAGE_STRING); | ||
125 | set_default_size(800,600); | ||
126 | show_all(); | ||
127 | w_status_bar.push(" "PACKAGE_STRING); | ||
128 | |||
129 | refresh_data(); | ||
130 | |||
131 | w_history.signal_cursor_changed().connect( | ||
132 | sigc::mem_fun(*this,&napkin_ui::on_history_cursor_changed)); | ||
133 | on_history_cursor_changed(); | ||
134 | w_history.signal_double_click().connect( | ||
135 | sigc::mem_fun(*this,&napkin_ui::on_history_double_click)); | ||
136 | } | ||
137 | |||
138 | void on_help_about() { | ||
139 | Gtk::AboutDialog about; | ||
140 | about.set_authors(vector<string>(1,"Michael Krelin <hacker@klever.net>")); | ||
141 | about.set_copyright("© 2008 Klever Group"); | ||
142 | extern const char *COPYING; | ||
143 | about.set_license(COPYING); | ||
144 | about.set_program_name(PACKAGE_NAME); | ||
145 | about.set_version(VERSION); | ||
146 | about.set_website("http://kin.klever.net/"); | ||
147 | about.set_website_label("Klever Internet Nothings"); | ||
148 | about.set_comments("The Sleeptracker PRO watch support program"); | ||
149 | about.run(); | ||
150 | } | ||
151 | |||
152 | void on_history_double_click() { | ||
153 | napkin::hypnodata_ptr_t hd = w_history.get_current(); | ||
154 | if(!hd) return; | ||
155 | napkin::gtk::hypnoinfo_dialog_t hid(*this); | ||
156 | hid.update_data(hd); | ||
157 | hid.add_button(Gtk::Stock::OK,Gtk::RESPONSE_OK); | ||
158 | hid.run(); | ||
159 | } | ||
160 | |||
161 | void refresh_data() { | ||
162 | load_data("ORDER BY s_alarm DESC"); | ||
163 | } | ||
164 | |||
165 | void load_data(const string& sql) { | ||
166 | list<napkin::hypnodata_ptr_t> hds; | ||
167 | db.load(hds,sql); | ||
168 | w_history.set_data(hds); | ||
169 | } | ||
170 | |||
171 | void on_history_cursor_changed() { | ||
172 | a_remove->set_sensitive(w_history.get_current()); | ||
173 | } | ||
174 | |||
175 | void on_remove() { | ||
176 | napkin::hypnodata_ptr_t hd = w_history.get_current(); | ||
177 | if(!hd) return; | ||
178 | napkin::gtk::hypnoinfo_dialog_t hid(*this); | ||
179 | hid.update_data(hd); | ||
180 | hid.add_button("Remove from the database",Gtk::RESPONSE_OK); | ||
181 | hid.add_button(Gtk::Stock::CANCEL,Gtk::RESPONSE_CANCEL); | ||
182 | if(hid.run() == Gtk::RESPONSE_OK) { | ||
183 | db.remove(*hd); // TODO: handle error | ||
184 | refresh_data(); | ||
185 | } | ||
186 | } | ||
187 | |||
188 | void on_quit() { | ||
189 | hide(); | ||
190 | } | ||
191 | |||
192 | void import_data(const napkin::hypnodata_ptr_t& hd) { | ||
193 | napkin::gtk::hypnoinfo_dialog_t hid(*this); | ||
194 | hid.update_data(hd); | ||
195 | hid.add_button("Add to the database",Gtk::RESPONSE_OK); | ||
196 | hid.add_button(Gtk::Stock::CANCEL,Gtk::RESPONSE_CANCEL); | ||
197 | if(hid.run() == Gtk::RESPONSE_OK) { | ||
198 | try { | ||
199 | db.store(*hd); | ||
200 | refresh_data(); | ||
201 | }catch(napkin::exception_db& nedb) { | ||
202 | Gtk::MessageDialog md(*this, | ||
203 | string("Failed to add data to the database... ")+nedb.what(), | ||
204 | false/*use_markup*/,Gtk::MESSAGE_ERROR,Gtk::BUTTONS_OK, | ||
205 | true/*modal*/); | ||
206 | md.run(); | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | |||
211 | class st_download_t : public Gtk::Dialog { | ||
212 | public: | ||
213 | Gtk::Label hint, attempt, error; | ||
214 | int nattempt; | ||
215 | napkin::hypnodata_ptr_t rv; | ||
216 | |||
217 | st_download_t(Gtk::Window& w) | ||
218 | : Gtk::Dialog("Importing data from watch",w,true/*modal*/,false/*use separator*/), | ||
219 | hint("\nImporting data from the sleeptracker...\n\n" | ||
220 | "Set your watch to the 'data' screen " | ||
221 | " and connect to the compuer, if you haven't yet.",0.5,0.5), | ||
222 | attempt("",1,0.5), error("",0,0.5), | ||
223 | nattempt(1), fd(-1) | ||
224 | { | ||
225 | Gtk::VBox *vb = get_vbox(); | ||
226 | vb->set_spacing(10); | ||
227 | hint.set_justify(Gtk::JUSTIFY_CENTER); | ||
228 | vb->pack_start(hint,Gtk::PACK_SHRINK,5); | ||
229 | vb->pack_start(attempt); | ||
230 | vb->pack_start(error); | ||
231 | add_button("Cancel",Gtk::RESPONSE_CANCEL); | ||
232 | vb->show_all(); | ||
233 | } | ||
234 | ~st_download_t() { | ||
235 | if(!(fd<0)) close(fd); | ||
236 | } | ||
237 | |||
238 | void on_map() { | ||
239 | Gtk::Dialog::on_map(); | ||
240 | initiate_attempt(); | ||
241 | } | ||
242 | |||
243 | void initiate_attempt() { | ||
244 | Glib::signal_timeout().connect_seconds( | ||
245 | sigc::mem_fun(*this,&st_download_t::try_watch), | ||
246 | 1); | ||
247 | } | ||
248 | void show_error(const napkin::exception& e) { | ||
249 | error.set_use_markup(true); | ||
250 | error.set_markup(string()+ | ||
251 | "<span color='red'>"+ | ||
252 | e.what()+"</span>"); | ||
253 | } | ||
254 | void next_attempt() { | ||
255 | char tmp[128]; | ||
256 | snprintf(tmp,sizeof(tmp),"Trying again, attempt #%d",++nattempt); | ||
257 | attempt.set_text(tmp); | ||
258 | } | ||
259 | |||
260 | int fd; | ||
261 | char buffer[512]; | ||
262 | size_t rb; | ||
263 | |||
264 | bool try_watch() { | ||
265 | try { | ||
266 | fd = napkin::sleeptracker::download_initiate(getenv("SLEEPTRACKER_PORT")); | ||
267 | Glib::signal_timeout().connect_seconds( | ||
268 | sigc::mem_fun(*this,&st_download_t::try_data), | ||
269 | 1); | ||
270 | return false; | ||
271 | }catch(napkin::exception_sleeptracker& nest) { | ||
272 | show_error(nest); | ||
273 | } | ||
274 | next_attempt(); | ||
275 | return true; | ||
276 | } | ||
277 | |||
278 | bool try_data() { | ||
279 | try { | ||
280 | try { | ||
281 | rb = napkin::sleeptracker::download_finish(fd,buffer,sizeof(buffer)); | ||
282 | }catch(napkin::exception_st_port& nestp) { | ||
283 | fd = -1; | ||
284 | show_error(nestp); | ||
285 | next_attempt(); | ||
286 | initiate_attempt(); | ||
287 | return false; | ||
288 | } | ||
289 | rv = napkin::sleeptracker::decode(buffer,rb); | ||
290 | response(Gtk::RESPONSE_OK); | ||
291 | }catch(napkin::exception_st_data_envelope& neste) { | ||
292 | show_error(neste); | ||
293 | next_attempt(); | ||
294 | initiate_attempt(); | ||
295 | }catch(napkin::exception_sleeptracker& nest) { | ||
296 | show_error(nest); | ||
297 | } | ||
298 | return false; | ||
299 | } | ||
300 | }; | ||
301 | |||
302 | void on_sleep_add_from_sleeptracker() { | ||
303 | st_download_t sd(*this); | ||
304 | if(sd.run()==Gtk::RESPONSE_OK && sd.rv ) { | ||
305 | sd.hide(); | ||
306 | #ifndef NDEBUG | ||
307 | { | ||
308 | ofstream dfile( | ||
309 | (db.datadir+"/raw-"+napkin::strftime("%Y-%m-%d.st",time(0))).c_str(), | ||
310 | std::ios::binary|std::ios::out|std::ios::trunc); | ||
311 | if(dfile) | ||
312 | dfile.write(sd.buffer,sd.rb); | ||
313 | dfile.close(); | ||
314 | } | ||
315 | #endif | ||
316 | import_data(sd.rv); | ||
317 | } | ||
318 | } | ||
319 | |||
320 | #ifndef NDEBUG | ||
321 | void on_sleep_add_from_datafile() { | ||
322 | Gtk::FileChooserDialog d("Please select a file", | ||
323 | Gtk::FILE_CHOOSER_ACTION_OPEN); | ||
324 | d.set_transient_for(*this); | ||
325 | d.add_button(Gtk::Stock::CANCEL,Gtk::RESPONSE_CANCEL); | ||
326 | d.add_button(Gtk::Stock::OPEN,Gtk::RESPONSE_OK); | ||
327 | Gtk::FileFilter stfiles; | ||
328 | stfiles.set_name("Sleeptracker files"); | ||
329 | stfiles.add_pattern("*.st"); | ||
330 | d.add_filter(stfiles); | ||
331 | Gtk::FileFilter allfiles; | ||
332 | allfiles.set_name("All files"); | ||
333 | allfiles.add_pattern("*"); | ||
334 | d.add_filter(allfiles); | ||
335 | if(d.run()==Gtk::RESPONSE_OK) { | ||
336 | d.hide(); | ||
337 | |||
338 | int fd = open(d.get_filename().c_str(),O_RDONLY); | ||
339 | if(fd<0) | ||
340 | throw napkin::exception("failed to open() data"); | ||
341 | unsigned char buffer[512]; | ||
342 | size_t rb = read(fd,buffer,sizeof(buffer)); | ||
343 | close(fd); | ||
344 | if( (rb==(size_t)-1) || rb==sizeof(buffer)) | ||
345 | throw napkin::exception("error reading datafile"); | ||
346 | napkin::hypnodata_ptr_t hd = napkin::sleeptracker::decode(buffer,rb); | ||
347 | import_data(hd); | ||
348 | } | ||
349 | } | ||
350 | #endif | ||
351 | |||
352 | #ifndef NDEBUG | ||
353 | void on_debug() { | ||
354 | } | ||
355 | #endif | ||
356 | }; | ||
357 | |||
358 | int main(int argc,char**argv) { | ||
359 | try { | ||
360 | Gtk::Main m(argc,argv); | ||
361 | napkin_ui hui; | ||
362 | m.run(hui); | ||
363 | return 0; | ||
364 | }catch(std::exception& e) { | ||
365 | cerr << "oops: " << e.what() << endl; | ||
366 | } | ||
367 | } | ||
diff --git a/src/schema.sql b/src/schema.sql new file mode 100644 index 0000000..f0fc4dd --- a/dev/null +++ b/src/schema.sql | |||
@@ -0,0 +1,9 @@ | |||
1 | CREATE TABLE sleeps ( | ||
2 | s_tobed text NOT NULL, -- w3cish timestamp with minute precision | ||
3 | s_alarm text NOT NULL PRIMARY KEY,-- w3cish timestamp with minute precision | ||
4 | s_window integer NOT NULL, -- number of minutes | ||
5 | s_data_a integer NOT NULL, -- number of seconds | ||
6 | s_almost_awakes text NOT NULL, -- [^0-9:TZ-]-separated list of w3cish | ||
7 | -- timestamps with second precision | ||
8 | s_timezone integer NOT NULL -- seconds west of GMT, TODO: make use of it | ||
9 | ); | ||
diff --git a/src/sleep_history.cc b/src/sleep_history.cc new file mode 100644 index 0000000..1b5ce27 --- a/dev/null +++ b/src/sleep_history.cc | |||
@@ -0,0 +1,158 @@ | |||
1 | #include <gtkmm/main.h> | ||
2 | #include <napkin/util.h> | ||
3 | |||
4 | #include "sleep_timeline.h" | ||
5 | #include "sleep_history.h" | ||
6 | |||
7 | namespace napkin { | ||
8 | namespace gtk { | ||
9 | |||
10 | sleep_history_t::basic_textrenderer::basic_textrenderer() { | ||
11 | property_family().set_value("monospace"); | ||
12 | property_single_paragraph_mode().set_value(true); | ||
13 | } | ||
14 | void sleep_history_t::basic_textrenderer::render_vfunc( | ||
15 | const Glib::RefPtr<Gdk::Drawable>& window, Gtk::Widget& widget, | ||
16 | const Gdk::Rectangle& background_area, const Gdk::Rectangle& cell_area, | ||
17 | const Gdk::Rectangle& expose_area, Gtk::CellRendererState flags) { | ||
18 | hypnodata_t *hd = (hypnodata_t*)property_user_data().get_value(); | ||
19 | property_text().set_value(hd?get_text(*hd).c_str():""); | ||
20 | Gtk::CellRendererText::render_vfunc(window,widget, | ||
21 | background_area,cell_area,expose_area, | ||
22 | flags); | ||
23 | } | ||
24 | |||
25 | const string sleep_history_t::date_render_t::get_text(const hypnodata_t& hd) const { | ||
26 | return hd.str_date(); } | ||
27 | const string sleep_history_t::tobed_render_t::get_text(const hypnodata_t& hd) const { | ||
28 | return hd.str_to_bed(); } | ||
29 | const string sleep_history_t::alarm_render_t::get_text(const hypnodata_t& hd) const { | ||
30 | return hd.str_alarm(); } | ||
31 | sleep_history_t::window_render_t::window_render_t() { | ||
32 | property_xalign().set_value(1); } | ||
33 | const string sleep_history_t::window_render_t::get_text( | ||
34 | const hypnodata_t& hd) const { | ||
35 | char tmp[16]; | ||
36 | snprintf(tmp,sizeof(tmp),"%2d",hd.window); | ||
37 | return tmp; | ||
38 | } | ||
39 | sleep_history_t::nawakes_render_t::nawakes_render_t() { | ||
40 | property_xalign().set_value(1); } | ||
41 | const string sleep_history_t::nawakes_render_t::get_text( | ||
42 | const hypnodata_t& hd) const { | ||
43 | char tmp[16]; | ||
44 | snprintf(tmp,sizeof(tmp),"%2d",(int)hd.almost_awakes.size()); | ||
45 | return tmp; | ||
46 | } | ||
47 | const string sleep_history_t::data_a_render_t::get_text(const hypnodata_t& hd) const { | ||
48 | return hd.str_data_a(); } | ||
49 | |||
50 | sleep_history_t::sleep_timeline_render_t::sleep_timeline_render_t( | ||
51 | const sleep_history_t& sh) : sleep_history(sh) | ||
52 | { | ||
53 | property_xpad().set_value(2); property_ypad().set_value(2); | ||
54 | } | ||
55 | void sleep_history_t::sleep_timeline_render_t::render_vfunc( | ||
56 | const Glib::RefPtr<Gdk::Drawable>& window, Gtk::Widget&/*widget*/, | ||
57 | const Gdk::Rectangle&/*background_area*/, const Gdk::Rectangle& cell_area, | ||
58 | const Gdk::Rectangle&/*expose_area*/, Gtk::CellRendererState/*flags*/) { | ||
59 | hypnodata_t *hd = (hypnodata_t*)property_user_data().get_value(); | ||
60 | if(!hd) return; | ||
61 | int xpad = property_xpad(), ypad = property_ypad(); | ||
62 | int x0 = cell_area.get_x()+xpad, y0 = cell_area.get_y()+ypad, | ||
63 | dx = cell_area.get_width()-2*xpad, dy = cell_area.get_height()-2*ypad; | ||
64 | time_t a = hd->aligned_start(); | ||
65 | gtk::render_sleep_timeline( | ||
66 | *hd, | ||
67 | window, | ||
68 | x0,y0, dx,dy, | ||
69 | a+sleep_history.min_tobed, | ||
70 | a+sleep_history.max_alarm ); | ||
71 | } | ||
72 | |||
73 | sleep_history_t::sleep_history_t(db_t& d) | ||
74 | : store( Gtk::ListStore::create(cols) ), | ||
75 | r_sleep_timeline(*this), | ||
76 | min_tobed(24*60*60*2), max_alarm(0), | ||
77 | db(d) | ||
78 | { | ||
79 | w_tree.set_model(store); | ||
80 | add(w_tree); | ||
81 | append_c("Date",r_date); | ||
82 | append_c("To bed",r_to_bed); | ||
83 | (c_timeline=append_c("Sleep timeline",r_sleep_timeline))->set_expand(true); | ||
84 | append_c("Alarm",r_alarm); | ||
85 | append_c("Window",r_window); | ||
86 | append_c(" N",r_nawakes); | ||
87 | append_c("Data A",r_data_a); | ||
88 | |||
89 | w_tree.signal_query_tooltip().connect( | ||
90 | sigc::mem_fun(*this,&sleep_history_t::on_query_tooltip)); | ||
91 | w_tree.set_has_tooltip(true); | ||
92 | w_tree.signal_button_press_event().connect( | ||
93 | sigc::mem_fun(*this,&sleep_history_t::on_button_press),false); | ||
94 | } | ||
95 | |||
96 | Gtk::TreeView::Column *sleep_history_t::append_c(const string& title,Gtk::CellRenderer& renderer) { | ||
97 | Gtk::TreeView::Column *rv = w_tree.get_column(w_tree.append_column(title,renderer)-1); | ||
98 | rv->add_attribute(renderer.property_user_data(),cols.c_hypnodata_ptr); | ||
99 | rv->set_resizable(true); | ||
100 | return rv; | ||
101 | } | ||
102 | |||
103 | bool sleep_history_t::on_button_press(GdkEventButton* geb) { | ||
104 | if(geb->type!=GDK_2BUTTON_PRESS) return false; | ||
105 | double_click_signal(); | ||
106 | return true; | ||
107 | } | ||
108 | |||
109 | bool sleep_history_t::on_query_tooltip( | ||
110 | int x,int y,bool keyboard_tooltip, | ||
111 | const Glib::RefPtr<Gtk::Tooltip>& tooltip) { | ||
112 | if(keyboard_tooltip) return false; | ||
113 | int tx,ty; | ||
114 | w_tree.convert_widget_to_tree_coords(x,y,tx,ty); | ||
115 | Gtk::TreeModel::Path p; | ||
116 | Gtk::TreeViewColumn *c; | ||
117 | int cx, cy; | ||
118 | if(!w_tree.get_path_at_pos(tx,ty,p,c,cx,cy)) return false; | ||
119 | if(c != c_timeline) return false; | ||
120 | hypnodata_ptr_t hd = store->get_iter(p)->get_value(cols.c_hypnodata); | ||
121 | string mup = "Almost awake moments are:\n\n<tt>"; | ||
122 | for(vector<time_t>::const_iterator i=hd->almost_awakes.begin();i!=hd->almost_awakes.end();++i) | ||
123 | mup += strftime(" %H:%M:%S\n",*i); | ||
124 | mup += "</tt>"; | ||
125 | tooltip->set_markup( mup ); | ||
126 | return true; | ||
127 | } | ||
128 | |||
129 | void sleep_history_t::set_data(list<napkin::hypnodata_ptr_t> data) { | ||
130 | store->clear(); | ||
131 | min_tobed = 24*60*60*2; max_alarm = 0; | ||
132 | for(std::list<napkin::hypnodata_ptr_t>::const_iterator | ||
133 | i=data.begin(); i!=data.end(); ++i) { | ||
134 | Gtk::TreeModel::Row r = *(store->append()); | ||
135 | r[cols.c_hypnodata] = *i; | ||
136 | r[cols.c_hypnodata_ptr] = i->get(); | ||
137 | time_t a = (*i)->aligned_start(); | ||
138 | time_t soff = (*i)->to_bed-a; | ||
139 | if(soff < min_tobed) min_tobed = soff; | ||
140 | time_t eoff = (*i)->alarm-a; | ||
141 | if(eoff > max_alarm) max_alarm = eoff; | ||
142 | } | ||
143 | while(Gtk::Main::events_pending()) Gtk::Main::iteration() ; | ||
144 | w_tree.columns_autosize(); | ||
145 | } | ||
146 | |||
147 | const hypnodata_ptr_t sleep_history_t::get_current() { | ||
148 | Gtk::TreeModel::Path p; | ||
149 | Gtk::TreeViewColumn *c; | ||
150 | w_tree.get_cursor(p,c); | ||
151 | if( (!p.gobj()) || p.empty()) | ||
152 | return hypnodata_ptr_t(); /* XXX: or throw? */ | ||
153 | Gtk::ListStore::iterator i = store->get_iter(p); | ||
154 | return i->get_value(cols.c_hypnodata); | ||
155 | } | ||
156 | |||
157 | } | ||
158 | } | ||
diff --git a/src/sleep_history.h b/src/sleep_history.h new file mode 100644 index 0000000..7837711 --- a/dev/null +++ b/src/sleep_history.h | |||
@@ -0,0 +1,113 @@ | |||
1 | #ifndef __N_SLEEP_HISTORY_H | ||
2 | #define __N_SLEEP_HISTORY_H | ||
3 | |||
4 | #include <string> | ||
5 | #include <gtkmm/scrolledwindow.h> | ||
6 | #include <gtkmm/treeview.h> | ||
7 | #include <gtkmm/liststore.h> | ||
8 | #include <napkin/types.h> | ||
9 | #include "db.h" | ||
10 | |||
11 | namespace napkin { | ||
12 | namespace gtk { | ||
13 | using std::string; | ||
14 | |||
15 | class sleep_history_t : public Gtk::ScrolledWindow { | ||
16 | public: | ||
17 | class basic_textrenderer : public Gtk::CellRendererText { | ||
18 | public: | ||
19 | basic_textrenderer(); | ||
20 | void render_vfunc( | ||
21 | const Glib::RefPtr<Gdk::Drawable>& window, Gtk::Widget& widget, | ||
22 | const Gdk::Rectangle& background_area, const Gdk::Rectangle& cell_area, | ||
23 | const Gdk::Rectangle& expose_area, Gtk::CellRendererState flags); | ||
24 | virtual const string get_text(const hypnodata_t& hd) const = 0; | ||
25 | }; | ||
26 | |||
27 | class date_render_t : public basic_textrenderer { | ||
28 | public: | ||
29 | const string get_text(const hypnodata_t& hd) const; | ||
30 | }; | ||
31 | class tobed_render_t : public basic_textrenderer { | ||
32 | public: | ||
33 | const string get_text(const hypnodata_t& hd) const; | ||
34 | }; | ||
35 | class alarm_render_t : public basic_textrenderer { | ||
36 | public: | ||
37 | const string get_text(const hypnodata_t& hd) const; | ||
38 | }; | ||
39 | class window_render_t : public basic_textrenderer { | ||
40 | public: | ||
41 | window_render_t(); | ||
42 | const string get_text(const hypnodata_t& hd) const; | ||
43 | }; | ||
44 | class nawakes_render_t : public basic_textrenderer { | ||
45 | public: | ||
46 | nawakes_render_t(); | ||
47 | const string get_text(const hypnodata_t& hd) const; | ||
48 | }; | ||
49 | class data_a_render_t : public basic_textrenderer { | ||
50 | public: | ||
51 | const string get_text(const hypnodata_t& hd) const; | ||
52 | }; | ||
53 | |||
54 | class sleep_timeline_render_t : public Gtk::CellRenderer { | ||
55 | public: | ||
56 | const sleep_history_t& sleep_history; | ||
57 | |||
58 | sleep_timeline_render_t(const sleep_history_t& sh); | ||
59 | void render_vfunc(const Glib::RefPtr<Gdk::Drawable>& window, Gtk::Widget&/*widget*/, | ||
60 | const Gdk::Rectangle&/*background_area*/, const Gdk::Rectangle& cell_area, | ||
61 | const Gdk::Rectangle&/*expose_area*/, Gtk::CellRendererState/*flags*/); | ||
62 | }; | ||
63 | |||
64 | class columns_t : public Gtk::TreeModel::ColumnRecord { | ||
65 | public: | ||
66 | Gtk::TreeModelColumn<hypnodata_ptr_t> c_hypnodata; | ||
67 | Gtk::TreeModelColumn<void*> c_hypnodata_ptr; | ||
68 | |||
69 | columns_t() { | ||
70 | add(c_hypnodata); add(c_hypnodata_ptr); | ||
71 | } | ||
72 | }; | ||
73 | |||
74 | columns_t cols; | ||
75 | Gtk::TreeView w_tree; | ||
76 | Glib::RefPtr<Gtk::ListStore> store; | ||
77 | date_render_t r_date; | ||
78 | tobed_render_t r_to_bed; | ||
79 | alarm_render_t r_alarm; | ||
80 | window_render_t r_window; | ||
81 | nawakes_render_t r_nawakes; | ||
82 | data_a_render_t r_data_a; | ||
83 | sleep_timeline_render_t r_sleep_timeline; | ||
84 | Gtk::TreeView::Column *c_timeline; | ||
85 | |||
86 | sigc::signal<void> double_click_signal; | ||
87 | sigc::signal<void>& signal_double_click() { return double_click_signal; } | ||
88 | |||
89 | time_t min_tobed, max_alarm; | ||
90 | |||
91 | db_t& db; | ||
92 | |||
93 | sleep_history_t(db_t& d); | ||
94 | |||
95 | Gtk::TreeView::Column *append_c(const string& title,Gtk::CellRenderer& renderer); | ||
96 | |||
97 | bool on_button_press(GdkEventButton* geb); | ||
98 | bool on_query_tooltip(int x,int y,bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip); | ||
99 | |||
100 | |||
101 | void set_data(list<napkin::hypnodata_ptr_t> data); | ||
102 | |||
103 | Glib::SignalProxy0<void> signal_cursor_changed() { | ||
104 | return w_tree.signal_cursor_changed(); | ||
105 | } | ||
106 | |||
107 | const hypnodata_ptr_t get_current(); | ||
108 | |||
109 | }; | ||
110 | } | ||
111 | } | ||
112 | |||
113 | #endif /* __N_SLEEP_HISTORY_H */ | ||
diff --git a/src/sleep_timeline.cc b/src/sleep_timeline.cc new file mode 100644 index 0000000..e5d4146 --- a/dev/null +++ b/src/sleep_timeline.cc | |||
@@ -0,0 +1,100 @@ | |||
1 | #include <cstdlib> | ||
2 | #include <vector> | ||
3 | #include <gtkmm/widget.h> | ||
4 | |||
5 | #include "sleep_timeline.h" | ||
6 | |||
7 | namespace napkin { | ||
8 | namespace gtk { | ||
9 | using std::vector; | ||
10 | using std::min; | ||
11 | |||
12 | void render_sleep_timeline( | ||
13 | const hypnodata_t& hd, | ||
14 | const Glib::RefPtr<Gdk::Drawable>& w, | ||
15 | int x0,int y0,int dx,int dy, | ||
16 | time_t _t0,time_t _t1) { | ||
17 | static Gdk::Color c_tobed("darkgreen"), c_alarm("red"), | ||
18 | c_almostawake("maroon"), c_midnight("blue"), c_hour("#606060"), | ||
19 | c_timeline("#404040"), c_window("red"), | ||
20 | c_background("#ffffc0"), c_border("gray"); | ||
21 | static bool beenthere=false; | ||
22 | if(!beenthere) { | ||
23 | Glib::RefPtr<Gdk::Colormap> cm(Gtk::Widget::get_default_colormap()); | ||
24 | cm->alloc_color(c_tobed); cm->alloc_color(c_alarm); | ||
25 | cm->alloc_color(c_almostawake); | ||
26 | cm->alloc_color(c_midnight); cm->alloc_color(c_hour); | ||
27 | cm->alloc_color(c_timeline); cm->alloc_color(c_window); | ||
28 | cm->alloc_color(c_background); cm->alloc_color(c_border); | ||
29 | beenthere = true; | ||
30 | } | ||
31 | Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(w); | ||
32 | |||
33 | gc->set_foreground(c_background); | ||
34 | w->draw_rectangle(gc,true, x0,y0, dx,dy+1 ); | ||
35 | gc->set_foreground(c_border); | ||
36 | w->draw_rectangle(gc,false, x0,y0, dx,dy+1 ); | ||
37 | x0+=3; dx-=6; | ||
38 | |||
39 | time_t t0, t1; | ||
40 | if(_t0 && _t1 && _t0!=_t1 && _t0<=hd.to_bed && hd.alarm<=_t1) | ||
41 | t0 = _t0, t1 = _t1; | ||
42 | else | ||
43 | t0 = hd.to_bed, t1 = hd.alarm; | ||
44 | time_t dt = t1-t0; | ||
45 | |||
46 | time_t tb = hd.to_bed; time_t ta = hd.alarm; | ||
47 | int xb = x0+dx*(tb-t0)/dt, | ||
48 | xa = x0+dx*(ta-t0)/dt; | ||
49 | int ym = y0+dy/2; | ||
50 | gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER); | ||
51 | gc->set_foreground(c_timeline); | ||
52 | w->draw_line(gc, xb,ym, xa,ym ); | ||
53 | time_t ws = ta-hd.window*60; | ||
54 | int xws = x0+dx*(ws-t0)/dt; | ||
55 | gc->set_foreground(c_window); | ||
56 | w->draw_rectangle(gc, true, xws,ym-1, xa-xws,3 ); | ||
57 | gc->set_foreground(c_almostawake); | ||
58 | int tl2 = min(dy/2 - 3, 7); | ||
59 | int yt0 = ym-tl2, yt1 = ym+tl2+1; | ||
60 | for(vector<time_t>::const_iterator i=hd.almost_awakes.begin();i!=hd.almost_awakes.end();++i) { | ||
61 | int x = x0+dx*(*i-t0)/dt; | ||
62 | w->draw_line(gc, x,yt0, x,yt1 ); | ||
63 | } | ||
64 | tl2 = min(dy/5, 5); | ||
65 | yt0 = ym-tl2; yt1 = ym+tl2+1; | ||
66 | gc->set_foreground(c_hour); | ||
67 | time_t midnight = hd.aligned_start()+24*60*60; | ||
68 | for(time_t h = tb-tb%3600 + 3600; h<ta ; h+=3600) { | ||
69 | if(h==midnight) gc->set_foreground(c_midnight); | ||
70 | int x = x0+dx*(h-t0)/dt; | ||
71 | w->draw_line(gc, x,yt0, x,yt1 ); | ||
72 | if(h==midnight) gc->set_foreground(c_hour); | ||
73 | } | ||
74 | gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER); | ||
75 | gc->set_foreground(c_tobed); | ||
76 | w->draw_line(gc, xb,yt0, xb,yt1 ); | ||
77 | gc->set_foreground(c_alarm); | ||
78 | w->draw_line(gc, xa,yt0, xa,yt1 ); | ||
79 | } | ||
80 | |||
81 | |||
82 | bool sleep_timeline_t::on_expose_event(GdkEventExpose*) { | ||
83 | if(!hd) return true; | ||
84 | Glib::RefPtr<Gdk::Window> w = get_window(); | ||
85 | int x0,y0,dx,dy,wd; | ||
86 | w->get_geometry(x0,y0,dx,dy,wd); | ||
87 | render_sleep_timeline( | ||
88 | *hd, | ||
89 | w, | ||
90 | 0,0, dx-2,dy-2 ); | ||
91 | return true; | ||
92 | } | ||
93 | |||
94 | void sleep_timeline_t::set_data(const hypnodata_ptr_t& _hd) { | ||
95 | hd = _hd; | ||
96 | queue_draw(); | ||
97 | } | ||
98 | |||
99 | } | ||
100 | } | ||
diff --git a/src/sleep_timeline.h b/src/sleep_timeline.h new file mode 100644 index 0000000..3264fd6 --- a/dev/null +++ b/src/sleep_timeline.h | |||
@@ -0,0 +1,29 @@ | |||
1 | #ifndef __N_SLEEP_TIMELINE_H | ||
2 | #define __N_SLEEP_TIMELINE_H | ||
3 | |||
4 | #include <time.h> | ||
5 | #include <gtkmm/drawingarea.h> | ||
6 | #include <napkin/types.h> | ||
7 | |||
8 | namespace napkin { | ||
9 | namespace gtk { | ||
10 | |||
11 | void render_sleep_timeline( | ||
12 | const hypnodata_t& hd, | ||
13 | const Glib::RefPtr<Gdk::Drawable>& w, | ||
14 | int x0,int y0,int dx,int dy, | ||
15 | time_t _t0=0,time_t _t1=0); | ||
16 | |||
17 | class sleep_timeline_t : public Gtk::DrawingArea { | ||
18 | public: | ||
19 | hypnodata_ptr_t hd; | ||
20 | |||
21 | bool on_expose_event(GdkEventExpose*); | ||
22 | void set_data(const hypnodata_ptr_t& _hd); | ||
23 | }; | ||
24 | |||
25 | |||
26 | } | ||
27 | } | ||
28 | |||
29 | #endif /* __N_SLEEP_TIMELINE_H */ | ||
diff --git a/src/sqlite.h b/src/sqlite.h new file mode 100644 index 0000000..ad276ee --- a/dev/null +++ b/src/sqlite.h | |||
@@ -0,0 +1,103 @@ | |||
1 | #ifndef __SQLITE_H | ||
2 | #define __SQLITE_H | ||
3 | |||
4 | #include <cassert> | ||
5 | #include <stdexcept> | ||
6 | #include <string> | ||
7 | #include <sqlite3.h> | ||
8 | |||
9 | namespace sqlite { | ||
10 | using std::string; | ||
11 | |||
12 | class exception : public std::runtime_error { | ||
13 | public: | ||
14 | int rcode; | ||
15 | explicit exception(const string& w,int rc=-1) | ||
16 | : std::runtime_error(w), rcode(rc) { } | ||
17 | ~exception() throw() { } | ||
18 | }; | ||
19 | |||
20 | class db_t { | ||
21 | public: | ||
22 | sqlite3 *_D; | ||
23 | |||
24 | db_t() | ||
25 | : _D(0) { } | ||
26 | db_t(const char *f) | ||
27 | : _D(0) { open(f); } | ||
28 | ~db_t() { close(); } | ||
29 | |||
30 | operator const sqlite3*(void) const { return _D; } | ||
31 | operator sqlite3*(void) { return _D; } | ||
32 | |||
33 | void close() { | ||
34 | if(_D) { | ||
35 | sqlite3_close(_D); | ||
36 | _D = 0; | ||
37 | } | ||
38 | } | ||
39 | void open(const char *f) { | ||
40 | close(); | ||
41 | int r = sqlite3_open(f,&_D); | ||
42 | if(r!=SQLITE_OK) { | ||
43 | string msg = sqlite3_errmsg(_D); sqlite3_close(_D); | ||
44 | throw exception("Failed to open SQLite database: "+msg,r); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | void exec(const char *sql) { | ||
49 | assert(_D); | ||
50 | char *errm; | ||
51 | int r = sqlite3_exec(_D,sql,NULL,NULL,&errm); | ||
52 | if(r!=SQLITE_OK) | ||
53 | throw exception(string("Failed to sqlite3_exec():")+errm,r); | ||
54 | } | ||
55 | void get_table(const char *sql,char ***resp,int *nr,int *nc) { | ||
56 | assert(_D); | ||
57 | char *errm; | ||
58 | int r = sqlite3_get_table(_D,sql,resp,nr,nc,&errm); | ||
59 | if(r!=SQLITE_OK) | ||
60 | throw exception(string("Failed to sqlite3_get_table():")+errm,r); | ||
61 | } | ||
62 | }; | ||
63 | |||
64 | template<typename T> | ||
65 | class mem_t { | ||
66 | public: | ||
67 | T _M; | ||
68 | |||
69 | mem_t(T M) :_M(M) { } | ||
70 | ~mem_t() { if(_M) sqlite3_free(_M); } | ||
71 | |||
72 | operator const T&(void) const { return _M; } | ||
73 | operator T&(void) { return _M; } | ||
74 | |||
75 | mem_t operator=(T M) { | ||
76 | if(_M) sqlite3_free(_M); | ||
77 | _M = M; | ||
78 | } | ||
79 | }; | ||
80 | |||
81 | class table_t { | ||
82 | public: | ||
83 | char **_T; | ||
84 | |||
85 | table_t() : _T(0) { } | ||
86 | table_t(char **T) : _T(T) { } | ||
87 | ~table_t() { if(_T) sqlite3_free_table(_T); } | ||
88 | |||
89 | operator char**&(void) { return _T; } | ||
90 | |||
91 | operator char ***(void) { | ||
92 | if(_T) sqlite3_free_table(_T); | ||
93 | return &_T; } | ||
94 | |||
95 | const char *get(int r,int c,int nc) { | ||
96 | assert(_T); | ||
97 | return _T[r*nc+c]; | ||
98 | } | ||
99 | }; | ||
100 | |||
101 | } | ||
102 | |||
103 | #endif /* __SQLITE_H */ | ||
diff --git a/src/widgets.cc b/src/widgets.cc new file mode 100644 index 0000000..ea85bc8 --- a/dev/null +++ b/src/widgets.cc | |||
@@ -0,0 +1,63 @@ | |||
1 | #include <napkin/util.h> | ||
2 | #include "widgets.h" | ||
3 | |||
4 | namespace napkin { | ||
5 | namespace gtk { | ||
6 | |||
7 | hypnoinfo_t::hypnoinfo_t() | ||
8 | : w_upper(4,3,false/*homogeneous*/), | ||
9 | lc_tobed("To bed:",0.5,0.5), | ||
10 | lc_timeline("Sleep timeline:",0.5,0.5), | ||
11 | lc_alarm("Alarm:",0.5,0.5), lc_window("Window:",0.5,0.5), | ||
12 | l_data_a("",0.9,0.5) | ||
13 | { | ||
14 | add(l_date); | ||
15 | add(l_hseparator); | ||
16 | w_upper.set_col_spacings(5); | ||
17 | w_upper.attach(lc_tobed,0,1,0,1, Gtk::SHRINK); | ||
18 | w_upper.attach(lc_timeline,1,2,0,1, Gtk::SHRINK); | ||
19 | w_upper.attach(lc_alarm,2,3,0,1, Gtk::SHRINK); | ||
20 | w_upper.attach(lf_tobed,0,1,1,4, Gtk::SHRINK); | ||
21 | w_upper.attach(st_timeline,1,2,1,4, | ||
22 | Gtk::FILL|Gtk::EXPAND,Gtk::FILL|Gtk::EXPAND,0,0); | ||
23 | w_upper.attach(lf_alarm,2,3,1,2, Gtk::SHRINK); | ||
24 | w_upper.attach(lc_window,2,3,2,3, Gtk::SHRINK); | ||
25 | w_upper.attach(lf_window,2,3,3,4, Gtk::SHRINK); | ||
26 | add(w_upper); | ||
27 | add(lc_almost_awakes); | ||
28 | add(lf_almost_awakes); | ||
29 | add(l_data_a); | ||
30 | show_all(); | ||
31 | } | ||
32 | |||
33 | void hypnoinfo_t::update_data(const hypnodata_ptr_t& hd) { | ||
34 | l_date.set_use_markup(true); | ||
35 | l_date.set_markup("<b>"+hd->str_date()+"</b>"); | ||
36 | lf_tobed.set_use_markup(true); | ||
37 | lf_tobed.set_markup("<b>"+hd->str_to_bed()+"</b>"); | ||
38 | lf_alarm.set_use_markup(true); | ||
39 | lf_alarm.set_markup("<b>"+hd->str_alarm()+"</b>"); | ||
40 | char tmp[64]; | ||
41 | snprintf(tmp,sizeof(tmp),"<b>%d mins</b>",hd->window); | ||
42 | lf_window.set_use_markup(true); | ||
43 | lf_window.set_markup(tmp); | ||
44 | snprintf(tmp,sizeof(tmp),"<b>%d</b> almost awake moments:",(int)hd->almost_awakes.size()); | ||
45 | lc_almost_awakes.set_use_markup(true); | ||
46 | lc_almost_awakes.set_markup(tmp); | ||
47 | string awlist; | ||
48 | for(vector<time_t>::const_iterator i=hd->almost_awakes.begin();i!=hd->almost_awakes.end();++i) { | ||
49 | if(!awlist.empty()) | ||
50 | awlist += ", "; | ||
51 | awlist += strftime("<b>%H:%M:%S</b>",*i); | ||
52 | } | ||
53 | lf_almost_awakes.set_use_markup(true); | ||
54 | lf_almost_awakes.set_line_wrap(true); | ||
55 | lf_almost_awakes.set_line_wrap_mode(Pango::WRAP_WORD); | ||
56 | lf_almost_awakes.set_markup("<tt>"+awlist+"</tt>"); | ||
57 | l_data_a.set_use_markup(true); | ||
58 | l_data_a.set_markup("Data A is <b>"+hd->str_data_a()+"</b>"); | ||
59 | st_timeline.set_data(hd); | ||
60 | } | ||
61 | |||
62 | } | ||
63 | } | ||
diff --git a/src/widgets.h b/src/widgets.h new file mode 100644 index 0000000..99936ff --- a/dev/null +++ b/src/widgets.h | |||
@@ -0,0 +1,33 @@ | |||
1 | #ifndef __N_WIDGETS_H | ||
2 | #define __N_WIDGETS_H | ||
3 | |||
4 | #include <gtkmm/box.h> | ||
5 | #include <gtkmm/label.h> | ||
6 | #include <gtkmm/separator.h> | ||
7 | #include <gtkmm/table.h> | ||
8 | #include "sleep_timeline.h" | ||
9 | |||
10 | namespace napkin { | ||
11 | namespace gtk { | ||
12 | |||
13 | class hypnoinfo_t : public Gtk::VBox { | ||
14 | public: | ||
15 | Gtk::Label l_date; | ||
16 | Gtk::HSeparator l_hseparator; | ||
17 | Gtk::Table w_upper; | ||
18 | Gtk::Label lc_tobed, lc_timeline, lc_alarm, lc_window; | ||
19 | Gtk::Label lf_tobed, lf_alarm, lf_window; | ||
20 | sleep_timeline_t st_timeline; | ||
21 | Gtk::Label lc_almost_awakes; | ||
22 | Gtk::Label lf_almost_awakes; | ||
23 | Gtk::Label l_data_a; | ||
24 | |||
25 | hypnoinfo_t(); | ||
26 | |||
27 | void update_data(const hypnodata_ptr_t& hd); | ||
28 | }; | ||
29 | |||
30 | } | ||
31 | } | ||
32 | |||
33 | #endif /* __N_WIDGETS_H */ | ||
diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..579d0cc --- a/dev/null +++ b/test/.gitignore | |||
@@ -0,0 +1,4 @@ | |||
1 | /sleeptracker-decode | ||
2 | /sleeptracker-decode.o | ||
3 | /.libs | ||
4 | /.deps | ||
diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..09c3ed7 --- a/dev/null +++ b/test/Makefile.am | |||
@@ -0,0 +1,7 @@ | |||
1 | noinst_PROGRAMS = sleeptracker-decode | ||
2 | |||
3 | INCLUDES = -I${top_srcdir}/include/ ${MODULES_CFLAGS} | ||
4 | LIBS = ${top_builddir}/lib/libnapkin.la | ||
5 | |||
6 | sleeptracker_decode_SOURCES = sleeptracker-decode.cc | ||
7 | sleeptracker_decode_DEPENDENCIES = ${LIBS} | ||
diff --git a/test/sleeptracker-decode.cc b/test/sleeptracker-decode.cc new file mode 100644 index 0000000..77d4fbd --- a/dev/null +++ b/test/sleeptracker-decode.cc | |||
@@ -0,0 +1,49 @@ | |||
1 | #include <sys/types.h> | ||
2 | #include <sys/stat.h> | ||
3 | #include <fcntl.h> | ||
4 | #include <unistd.h> | ||
5 | #include <termios.h> | ||
6 | #include <iostream> | ||
7 | #include <stdexcept> | ||
8 | #include <algorithm> | ||
9 | #include <iterator> | ||
10 | using namespace std; | ||
11 | #include <napkin/st/decode.h> | ||
12 | |||
13 | string str_f_time(const char *fmt,time_t t) { | ||
14 | struct tm tt; | ||
15 | localtime_r(&t,&tt); | ||
16 | char rv[1024]; | ||
17 | strftime(rv,sizeof(rv),fmt,&tt); | ||
18 | return rv; | ||
19 | } | ||
20 | |||
21 | ostream& operator<<(ostream& o,const napkin::hypnodata_t& hd) { | ||
22 | o | ||
23 | << "Window is " << hd.window << endl | ||
24 | << "'To bed' time is " << str_f_time("%Y-%m-%d %H:%M",hd.to_bed) << endl | ||
25 | << "Alarm time is " << str_f_time("%Y-%m-%d %H:%M",hd.alarm) << endl | ||
26 | << "Data A is " << hd.data_a/60 << ":" << hd.data_a%60 << endl; | ||
27 | for(vector<time_t>::const_iterator i=hd.almost_awakes.begin();i!=hd.almost_awakes.end();++i) | ||
28 | o << " almost awake at " << str_f_time("%Y-%m-%d %H:%M:%S",*i) << endl; | ||
29 | return o; | ||
30 | } | ||
31 | |||
32 | int main(int/*argc*/,char **argv) { | ||
33 | try { | ||
34 | int fd = open(argv[1],O_RDONLY); | ||
35 | if(fd<0) | ||
36 | throw runtime_error("failed to open() data"); | ||
37 | unsigned char buffer[1024]; | ||
38 | int rb = read(fd,buffer,sizeof(buffer)); | ||
39 | if(!(rb>0)) | ||
40 | throw runtime_error("failed to read() data"); | ||
41 | close(fd); | ||
42 | |||
43 | napkin::hypnodata_t hd; | ||
44 | cout << napkin::sleeptracker::decode(hd,buffer,rb); | ||
45 | }catch(exception& e) { | ||
46 | cerr << "oops: " << e.what() << endl; | ||
47 | } | ||
48 | } | ||
49 | |||