summaryrefslogtreecommitdiffabout
authorMichael Krelin <hacker@klever.net>2008-04-05 11:17:33 (UTC)
committer Michael Krelin <hacker@klever.net>2008-04-05 11:17:33 (UTC)
commit04fb190243442e83349f129b523ab747e58100bf (patch) (unidiff)
treeddc28357fbe78b07fd3a5e0aa8088130bf305829
downloadnapkin-04fb190243442e83349f129b523ab747e58100bf.zip
napkin-04fb190243442e83349f129b523ab747e58100bf.tar.gz
napkin-04fb190243442e83349f129b523ab747e58100bf.tar.bz2
Initial commit into public repository0.0
Signed-off-by: Michael Krelin <hacker@klever.net>
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--.gitignore14
-rw-r--r--AUTHORS8
-rw-r--r--COPYING19
-rw-r--r--ChangeLog0
-rw-r--r--Makefile.am15
-rw-r--r--NEWS.xml6
-rw-r--r--NEWS.xsl24
-rw-r--r--README15
-rw-r--r--autogen.sh7
-rw-r--r--configure.ac66
-rw-r--r--include/Makefile.am4
-rw-r--r--include/napkin/exception.h36
-rw-r--r--include/napkin/st/decode.h15
-rw-r--r--include/napkin/st/download.h20
-rw-r--r--include/napkin/types.h46
-rw-r--r--include/napkin/util.h14
-rw-r--r--lib/.gitignore11
-rw-r--r--lib/Makefile.am11
-rw-r--r--lib/hypnodata.cc92
-rw-r--r--lib/st-decode.cc121
-rw-r--r--lib/st-download.cc70
-rw-r--r--lib/util.cc13
-rw-r--r--src/.gitignore13
-rw-r--r--src/Makefile.am32
-rw-r--r--src/db.cc101
-rw-r--r--src/db.h28
-rw-r--r--src/dialogs.cc17
-rw-r--r--src/dialogs.h25
-rw-r--r--src/napkin.cc367
-rw-r--r--src/schema.sql9
-rw-r--r--src/sleep_history.cc158
-rw-r--r--src/sleep_history.h113
-rw-r--r--src/sleep_timeline.cc100
-rw-r--r--src/sleep_timeline.h29
-rw-r--r--src/sqlite.h103
-rw-r--r--src/widgets.cc63
-rw-r--r--src/widgets.h33
-rw-r--r--test/.gitignore4
-rw-r--r--test/Makefile.am7
-rw-r--r--test/sleeptracker-decode.cc49
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
2Makefile
3Makefile.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
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..3a5571d
--- a/dev/null
+++ b/AUTHORS
@@ -0,0 +1,8 @@
1Klever dissected:
2 Michael 'hacker' Krelin <hacker@klever.net>
3 Leonid Ivanov <kamel@klever.net>
4
5The credits also go to the unknown hacker called Joe for dissecting
6sleeptracker data -
7http://www.sleeptracker-tec.de/tools/phorum/read.php?2,19
8I hope I find the way to contact him and give him proper credit.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..31595a2
--- a/dev/null
+++ b/COPYING
@@ -0,0 +1,19 @@
1Copyright (c) 2008 Klever Group (http://www.klever.net/)
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of
4this software and associated documentation files (the "Software"), to deal in
5the Software without restriction, including without limitation the rights to
6use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7of the Software, and to permit persons to whom the Software is furnished to do
8so, subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in all
11copies or substantial portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19SOFTWARE.
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 @@
1SUBDIRS=include lib src test
2EXTRA_DIST = NEWS NEWS.xml NEWS.xsl
3
4all-local: NEWS
5
6NEWS: NEWS.xsl NEWS.xml
7 ${XSLTPROC} -o $@ NEWS.xsl NEWS.xml
8
9ISSUEFILES = $$(find ${top_srcdir} -type f '(' \
10 -name '*.cc' -or -name '*.h' -or -name '*.sql' \
11 ')' ) \
12 ${top_srcdir}/configure.ac
13issues: todo fixme xxx
14todo 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,')&#xA;')"/>
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>&#xA;</xsl:text>
21 </xsl:template>
22 <xsl:template match="*|text()"/>
23
24</xsl:stylesheet>
diff --git a/README b/README
new file mode 100644
index 0000000..f353884
--- a/dev/null
+++ b/README
@@ -0,0 +1,15 @@
1
2Napkin expects you to have sleeptracker port at /dev/sleeptracker
3
4On linux you'd need to have 'ftdi_sio' kernel module loaded (and usb support,
5of course) and something along these lines in your udev rules:
6
7BUS=="usb", SYSFS{product}=="FT232R USB UART", SYSFS{serial}=="********", SYSFS{manufacturer}=="FTDI", KERNEL=="ttyUSB*", SYMLINK="sleeptracker", MODE="660", GROUP="usb"
8
9Note, that the I've wiped out the serial number, so that you don't try to use
10mine. Perhaps it will differ, although I have only one device.
11
12You can also set the other port name using SLEEPTRACKER_PORT environment
13variable, like
14
15env 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
2test -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 @@
1AC_INIT([napkin], [0.0], [napkin-bugs@klever.net])
2AC_CONFIG_AUX_DIR([aux.d])
3AC_CONFIG_SRCDIR([src/napkin.cc])
4AC_CONFIG_HEADERS([config.h])
5AM_INIT_AUTOMAKE([dist-bzip2])
6
7AC_PROG_INSTALL
8AC_PROG_CXX
9AC_PROG_CC
10AC_PROG_LIBTOOL
11PKG_PROG_PKG_CONFIG
12
13AC_HEADER_STDC
14
15AC_PATH_PROG([XSLTPROC],[xsltproc],[true])
16
17PKG_CHECK_MODULES([MODULES],[gtkmm-2.4 sqlite3],,[
18 AC_MSG_ERROR([not all dependencies could be satisfied])
19])
20
21AC_MSG_CHECKING([whether to enable debugging code])
22ndebug=true
23AC_ARG_ENABLE([debug],
24 AC_HELP_STRING([--enable-debug],[enable debugging/development code]),
25 [ test "$enableval" = "no" || ndebug=false ]
26)
27if $ndebug ; then
28 AC_MSG_RESULT([no])
29 CPPFLAGS="${CPPFLAGS}-DNDEBUG"
30else
31 AC_MSG_RESULT([yes])
32fi
33
34nitpick=false
35AC_MSG_CHECKING([whether to enable compiler nitpicking])
36AC_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)
40if $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"
52else
53 AC_MSG_RESULT([no])
54fi
55
56
57AC_CONFIG_FILES([
58 Makefile
59 include/Makefile
60 lib/Makefile
61 src/Makefile
62 test/Makefile
63])
64AC_OUTPUT
65
66dnl 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 @@
1include_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
14namespace 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
6namespace 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
6namespace 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
9namespace 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
7namespace 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 @@
1lib_LTLIBRARIES = libnapkin.la
2
3INCLUDES = -I${top_builddir}/include/ -I${top_srcdir}/include/ \
4 ${MODULES_CFLAGS}
5LIBS = ${MODULES_CFLAGS}
6
7libnapkin_la_SOURCES = \
8 st-decode.cc st-download.cc \
9 hypnodata.cc \
10 util.cc
11libnapkin_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
5namespace 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
6namespace 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
11namespace 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
3namespace 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 @@
1bin_PROGRAMS = napkin
2
3AM_CXXFLAGS = ${MODULES_CFLAGS} -I${top_srcdir}/include/ -I${srcdir}
4LIBS = ${MODULES_LIBS} \
5 ${top_builddir}/lib/libnapkin.la
6
7noinst_HEADERS = sqlite.h db.h \
8 sleep_timeline.h \
9 widgets.h dialogs.h \
10 sleep_history.h
11
12napkin_SOURCES = napkin.cc \
13 db.cc \
14 sleep_timeline.cc \
15 widgets.cc dialogs.cc \
16 sleep_history.cc \
17 schema.cc COPYING.cc
18napkin_DEPENDENCIES = \
19 ${top_builddir}/lib/libnapkin.la
20
21EXTRA_DIST = schema.sql
22
23schema.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 ) >$@
29COPYING.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
10namespace 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
9namespace 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
3namespace 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
8namespace 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>
3using std::cerr;
4using std::endl;
5#include <fstream>
6using std::ofstream;
7#include <cstdlib>
8using std::min;
9#include <stdexcept>
10using std::runtime_error;
11#include <list>
12using std::list;
13#include <vector>
14using std::vector;
15#include <string>
16using 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
39class 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
358int 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 @@
1CREATE 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
7namespace 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
11namespace 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
7namespace 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
8namespace 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
9namespace 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
4namespace 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
10namespace 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 @@
1noinst_PROGRAMS = sleeptracker-decode
2
3INCLUDES = -I${top_srcdir}/include/ ${MODULES_CFLAGS}
4LIBS = ${top_builddir}/lib/libnapkin.la
5
6sleeptracker_decode_SOURCES = sleeptracker-decode.cc
7sleeptracker_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>
10using namespace std;
11#include <napkin/st/decode.h>
12
13string 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
21ostream& 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
32int 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