summaryrefslogtreecommitdiffabout
path: root/src/napkin.cc
Unidiff
Diffstat (limited to 'src/napkin.cc') (more/less context) (ignore whitespace changes)
-rw-r--r--src/napkin.cc367
1 files changed, 367 insertions, 0 deletions
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}