summaryrefslogtreecommitdiffabout
path: root/src
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 /src
downloadnapkin-0.0.zip
napkin-0.0.tar.gz
napkin-0.0.tar.bz2
Initial commit into public repository0.0
Signed-off-by: Michael Krelin <hacker@klever.net>
Diffstat (limited to 'src') (more/less context) (ignore whitespace changes)
-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
15 files changed, 1191 insertions, 0 deletions
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 */