-rw-r--r-- | src/napkin.cc | 367 |
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> | ||
3 | using std::cerr; | ||
4 | using std::endl; | ||
5 | #include <fstream> | ||
6 | using std::ofstream; | ||
7 | #include <cstdlib> | ||
8 | using std::min; | ||
9 | #include <stdexcept> | ||
10 | using std::runtime_error; | ||
11 | #include <list> | ||
12 | using std::list; | ||
13 | #include <vector> | ||
14 | using std::vector; | ||
15 | #include <string> | ||
16 | using std::string; | ||
17 | #include <gtkmm/main.h> | ||
18 | #include <gtkmm/window.h> | ||
19 | #include <gtkmm/box.h> | ||
20 | #include <gtkmm/statusbar.h> | ||
21 | #include <gtkmm/uimanager.h> | ||
22 | #include <gtkmm/stock.h> | ||
23 | #include <gtkmm/toolbar.h> | ||
24 | #include <gtkmm/filechooserdialog.h> | ||
25 | #include <gtkmm/messagedialog.h> | ||
26 | #include <gtkmm/aboutdialog.h> | ||
27 | #include <napkin/exception.h> | ||
28 | #include <napkin/util.h> | ||
29 | #include <napkin/st/decode.h> | ||
30 | #include <napkin/st/download.h> | ||
31 | |||
32 | #include "db.h" | ||
33 | #include "sleep_timeline.h" | ||
34 | #include "dialogs.h" | ||
35 | #include "sleep_history.h" | ||
36 | |||
37 | #include "config.h" | ||
38 | |||
39 | class napkin_ui : public Gtk::Window { | ||
40 | public: | ||
41 | Gtk::VBox w_outer_box; | ||
42 | Gtk::Statusbar w_status_bar; | ||
43 | napkin::gtk::sleep_history_t w_history; | ||
44 | Glib::RefPtr<Gtk::UIManager> uiman; | ||
45 | Glib::RefPtr<Gtk::ActionGroup> agroup; | ||
46 | napkin::db_t db; | ||
47 | Glib::RefPtr<Gtk::Action> a_remove; | ||
48 | |||
49 | napkin_ui() | ||
50 | : w_history(db) | ||
51 | { | ||
52 | static char *ui_info = | ||
53 | "<ui>" | ||
54 | "<menubar name='menu_bar'>" | ||
55 | "<menu action='menu_sleep'>" | ||
56 | #ifndef NDEBUG | ||
57 | "<menu action='menu_sleep_add'>" | ||
58 | #endif | ||
59 | "<menuitem action='sleep_add_from_sleeptracker'/>" | ||
60 | #ifndef NDEBUG | ||
61 | "<menuitem action='sleep_add_from_datafile'/>" | ||
62 | "</menu>" | ||
63 | #endif | ||
64 | "<menuitem action='sleep_remove'/>" | ||
65 | "<menuitem action='exit'/>" | ||
66 | "</menu>" | ||
67 | "<menu action='menu_help'>" | ||
68 | "<menuitem action='help_about'/>" | ||
69 | "</menu>" | ||
70 | "</menubar>" | ||
71 | "<toolbar action='tool_bar'>" | ||
72 | "<toolitem action='sleep_add_from_sleeptracker'/>" | ||
73 | "<toolitem action='sleep_remove'/>" | ||
74 | "<separator expand='true'/>" | ||
75 | #ifndef NDEBUG | ||
76 | "<toolitem action='debug'/>" | ||
77 | "<separator/>" | ||
78 | #endif | ||
79 | "<toolitem action='exit'/>" | ||
80 | "</toolbar>" | ||
81 | "</ui>"; | ||
82 | agroup = Gtk::ActionGroup::create(); | ||
83 | agroup->add(Gtk::Action::create("menu_sleep","Sleep")); | ||
84 | agroup->add(Gtk::Action::create("menu_sleep_add","Add")); | ||
85 | agroup->add(Gtk::Action::create("sleep_add_from_sleeptracker",Gtk::Stock::CONNECT, | ||
86 | "from sleeptracker","import sleeptracker data from watch"), | ||
87 | Gtk::AccelKey("<Ctrl>d"), | ||
88 | sigc::mem_fun(*this,&napkin_ui::on_sleep_add_from_sleeptracker)); | ||
89 | #ifndef NDEBUG | ||
90 | agroup->add(Gtk::Action::create("sleep_add_from_datafile",Gtk::Stock::CONVERT, | ||
91 | "from data file","import sleeptracker data stored in a file"), | ||
92 | sigc::mem_fun(*this,&napkin_ui::on_sleep_add_from_datafile)); | ||
93 | #endif | ||
94 | agroup->add(a_remove=Gtk::Action::create("sleep_remove",Gtk::Stock::REMOVE, | ||
95 | "Remove","remove highlighted sleep event from the database"), | ||
96 | Gtk::AccelKey("delete"), | ||
97 | sigc::mem_fun(*this,&napkin_ui::on_remove)); | ||
98 | agroup->add(Gtk::Action::create("exit",Gtk::Stock::QUIT,"Exit","Exit "PACKAGE_NAME), | ||
99 | Gtk::AccelKey("<control>w"), | ||
100 | sigc::mem_fun(*this,&napkin_ui::on_quit)); | ||
101 | agroup->add(Gtk::Action::create("menu_help","Help")); | ||
102 | agroup->add(Gtk::Action::create("help_about",Gtk::Stock::ABOUT, | ||
103 | "About","About this program"), | ||
104 | sigc::mem_fun(*this,&napkin_ui::on_help_about)); | ||
105 | #ifndef NDEBUG | ||
106 | agroup->add(Gtk::Action::create("debug",Gtk::Stock::INFO,"Debug","debug action"), | ||
107 | sigc::mem_fun(*this,&napkin_ui::on_debug)); | ||
108 | #endif | ||
109 | uiman = Gtk::UIManager::create(); | ||
110 | uiman->insert_action_group(agroup); | ||
111 | add_accel_group(uiman->get_accel_group()); | ||
112 | uiman->add_ui_from_string(ui_info); | ||
113 | Gtk::Widget * mb = uiman->get_widget("/menu_bar"); | ||
114 | if(mb) | ||
115 | w_outer_box.pack_start(*mb,Gtk::PACK_SHRINK); | ||
116 | Gtk::Widget * tb = uiman->get_widget("/tool_bar"); | ||
117 | if(tb) { | ||
118 | static_cast<Gtk::Toolbar*>(tb)->set_toolbar_style(Gtk::TOOLBAR_ICONS); | ||
119 | w_outer_box.pack_start(*tb,Gtk::PACK_SHRINK); | ||
120 | } | ||
121 | w_outer_box.pack_start(w_history,true/*expand*/,true/*fill*/); | ||
122 | w_outer_box.pack_end(w_status_bar,false/*expand*/,false/*fill*/); | ||
123 | add(w_outer_box); | ||
124 | set_title(PACKAGE_STRING); | ||
125 | set_default_size(800,600); | ||
126 | show_all(); | ||
127 | w_status_bar.push(" "PACKAGE_STRING); | ||
128 | |||
129 | refresh_data(); | ||
130 | |||
131 | w_history.signal_cursor_changed().connect( | ||
132 | sigc::mem_fun(*this,&napkin_ui::on_history_cursor_changed)); | ||
133 | on_history_cursor_changed(); | ||
134 | w_history.signal_double_click().connect( | ||
135 | sigc::mem_fun(*this,&napkin_ui::on_history_double_click)); | ||
136 | } | ||
137 | |||
138 | void on_help_about() { | ||
139 | Gtk::AboutDialog about; | ||
140 | about.set_authors(vector<string>(1,"Michael Krelin <hacker@klever.net>")); | ||
141 | about.set_copyright("© 2008 Klever Group"); | ||
142 | extern const char *COPYING; | ||
143 | about.set_license(COPYING); | ||
144 | about.set_program_name(PACKAGE_NAME); | ||
145 | about.set_version(VERSION); | ||
146 | about.set_website("http://kin.klever.net/"); | ||
147 | about.set_website_label("Klever Internet Nothings"); | ||
148 | about.set_comments("The Sleeptracker PRO watch support program"); | ||
149 | about.run(); | ||
150 | } | ||
151 | |||
152 | void on_history_double_click() { | ||
153 | napkin::hypnodata_ptr_t hd = w_history.get_current(); | ||
154 | if(!hd) return; | ||
155 | napkin::gtk::hypnoinfo_dialog_t hid(*this); | ||
156 | hid.update_data(hd); | ||
157 | hid.add_button(Gtk::Stock::OK,Gtk::RESPONSE_OK); | ||
158 | hid.run(); | ||
159 | } | ||
160 | |||
161 | void refresh_data() { | ||
162 | load_data("ORDER BY s_alarm DESC"); | ||
163 | } | ||
164 | |||
165 | void load_data(const string& sql) { | ||
166 | list<napkin::hypnodata_ptr_t> hds; | ||
167 | db.load(hds,sql); | ||
168 | w_history.set_data(hds); | ||
169 | } | ||
170 | |||
171 | void on_history_cursor_changed() { | ||
172 | a_remove->set_sensitive(w_history.get_current()); | ||
173 | } | ||
174 | |||
175 | void on_remove() { | ||
176 | napkin::hypnodata_ptr_t hd = w_history.get_current(); | ||
177 | if(!hd) return; | ||
178 | napkin::gtk::hypnoinfo_dialog_t hid(*this); | ||
179 | hid.update_data(hd); | ||
180 | hid.add_button("Remove from the database",Gtk::RESPONSE_OK); | ||
181 | hid.add_button(Gtk::Stock::CANCEL,Gtk::RESPONSE_CANCEL); | ||
182 | if(hid.run() == Gtk::RESPONSE_OK) { | ||
183 | db.remove(*hd); // TODO: handle error | ||
184 | refresh_data(); | ||
185 | } | ||
186 | } | ||
187 | |||
188 | void on_quit() { | ||
189 | hide(); | ||
190 | } | ||
191 | |||
192 | void import_data(const napkin::hypnodata_ptr_t& hd) { | ||
193 | napkin::gtk::hypnoinfo_dialog_t hid(*this); | ||
194 | hid.update_data(hd); | ||
195 | hid.add_button("Add to the database",Gtk::RESPONSE_OK); | ||
196 | hid.add_button(Gtk::Stock::CANCEL,Gtk::RESPONSE_CANCEL); | ||
197 | if(hid.run() == Gtk::RESPONSE_OK) { | ||
198 | try { | ||
199 | db.store(*hd); | ||
200 | refresh_data(); | ||
201 | }catch(napkin::exception_db& nedb) { | ||
202 | Gtk::MessageDialog md(*this, | ||
203 | string("Failed to add data to the database... ")+nedb.what(), | ||
204 | false/*use_markup*/,Gtk::MESSAGE_ERROR,Gtk::BUTTONS_OK, | ||
205 | true/*modal*/); | ||
206 | md.run(); | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | |||
211 | class st_download_t : public Gtk::Dialog { | ||
212 | public: | ||
213 | Gtk::Label hint, attempt, error; | ||
214 | int nattempt; | ||
215 | napkin::hypnodata_ptr_t rv; | ||
216 | |||
217 | st_download_t(Gtk::Window& w) | ||
218 | : Gtk::Dialog("Importing data from watch",w,true/*modal*/,false/*use separator*/), | ||
219 | hint("\nImporting data from the sleeptracker...\n\n" | ||
220 | "Set your watch to the 'data' screen " | ||
221 | " and connect to the compuer, if you haven't yet.",0.5,0.5), | ||
222 | attempt("",1,0.5), error("",0,0.5), | ||
223 | nattempt(1), fd(-1) | ||
224 | { | ||
225 | Gtk::VBox *vb = get_vbox(); | ||
226 | vb->set_spacing(10); | ||
227 | hint.set_justify(Gtk::JUSTIFY_CENTER); | ||
228 | vb->pack_start(hint,Gtk::PACK_SHRINK,5); | ||
229 | vb->pack_start(attempt); | ||
230 | vb->pack_start(error); | ||
231 | add_button("Cancel",Gtk::RESPONSE_CANCEL); | ||
232 | vb->show_all(); | ||
233 | } | ||
234 | ~st_download_t() { | ||
235 | if(!(fd<0)) close(fd); | ||
236 | } | ||
237 | |||
238 | void on_map() { | ||
239 | Gtk::Dialog::on_map(); | ||
240 | initiate_attempt(); | ||
241 | } | ||
242 | |||
243 | void initiate_attempt() { | ||
244 | Glib::signal_timeout().connect_seconds( | ||
245 | sigc::mem_fun(*this,&st_download_t::try_watch), | ||
246 | 1); | ||
247 | } | ||
248 | void show_error(const napkin::exception& e) { | ||
249 | error.set_use_markup(true); | ||
250 | error.set_markup(string()+ | ||
251 | "<span color='red'>"+ | ||
252 | e.what()+"</span>"); | ||
253 | } | ||
254 | void next_attempt() { | ||
255 | char tmp[128]; | ||
256 | snprintf(tmp,sizeof(tmp),"Trying again, attempt #%d",++nattempt); | ||
257 | attempt.set_text(tmp); | ||
258 | } | ||
259 | |||
260 | int fd; | ||
261 | char buffer[512]; | ||
262 | size_t rb; | ||
263 | |||
264 | bool try_watch() { | ||
265 | try { | ||
266 | fd = napkin::sleeptracker::download_initiate(getenv("SLEEPTRACKER_PORT")); | ||
267 | Glib::signal_timeout().connect_seconds( | ||
268 | sigc::mem_fun(*this,&st_download_t::try_data), | ||
269 | 1); | ||
270 | return false; | ||
271 | }catch(napkin::exception_sleeptracker& nest) { | ||
272 | show_error(nest); | ||
273 | } | ||
274 | next_attempt(); | ||
275 | return true; | ||
276 | } | ||
277 | |||
278 | bool try_data() { | ||
279 | try { | ||
280 | try { | ||
281 | rb = napkin::sleeptracker::download_finish(fd,buffer,sizeof(buffer)); | ||
282 | }catch(napkin::exception_st_port& nestp) { | ||
283 | fd = -1; | ||
284 | show_error(nestp); | ||
285 | next_attempt(); | ||
286 | initiate_attempt(); | ||
287 | return false; | ||
288 | } | ||
289 | rv = napkin::sleeptracker::decode(buffer,rb); | ||
290 | response(Gtk::RESPONSE_OK); | ||
291 | }catch(napkin::exception_st_data_envelope& neste) { | ||
292 | show_error(neste); | ||
293 | next_attempt(); | ||
294 | initiate_attempt(); | ||
295 | }catch(napkin::exception_sleeptracker& nest) { | ||
296 | show_error(nest); | ||
297 | } | ||
298 | return false; | ||
299 | } | ||
300 | }; | ||
301 | |||
302 | void on_sleep_add_from_sleeptracker() { | ||
303 | st_download_t sd(*this); | ||
304 | if(sd.run()==Gtk::RESPONSE_OK && sd.rv ) { | ||
305 | sd.hide(); | ||
306 | #ifndef NDEBUG | ||
307 | { | ||
308 | ofstream dfile( | ||
309 | (db.datadir+"/raw-"+napkin::strftime("%Y-%m-%d.st",time(0))).c_str(), | ||
310 | std::ios::binary|std::ios::out|std::ios::trunc); | ||
311 | if(dfile) | ||
312 | dfile.write(sd.buffer,sd.rb); | ||
313 | dfile.close(); | ||
314 | } | ||
315 | #endif | ||
316 | import_data(sd.rv); | ||
317 | } | ||
318 | } | ||
319 | |||
320 | #ifndef NDEBUG | ||
321 | void on_sleep_add_from_datafile() { | ||
322 | Gtk::FileChooserDialog d("Please select a file", | ||
323 | Gtk::FILE_CHOOSER_ACTION_OPEN); | ||
324 | d.set_transient_for(*this); | ||
325 | d.add_button(Gtk::Stock::CANCEL,Gtk::RESPONSE_CANCEL); | ||
326 | d.add_button(Gtk::Stock::OPEN,Gtk::RESPONSE_OK); | ||
327 | Gtk::FileFilter stfiles; | ||
328 | stfiles.set_name("Sleeptracker files"); | ||
329 | stfiles.add_pattern("*.st"); | ||
330 | d.add_filter(stfiles); | ||
331 | Gtk::FileFilter allfiles; | ||
332 | allfiles.set_name("All files"); | ||
333 | allfiles.add_pattern("*"); | ||
334 | d.add_filter(allfiles); | ||
335 | if(d.run()==Gtk::RESPONSE_OK) { | ||
336 | d.hide(); | ||
337 | |||
338 | int fd = open(d.get_filename().c_str(),O_RDONLY); | ||
339 | if(fd<0) | ||
340 | throw napkin::exception("failed to open() data"); | ||
341 | unsigned char buffer[512]; | ||
342 | size_t rb = read(fd,buffer,sizeof(buffer)); | ||
343 | close(fd); | ||
344 | if( (rb==(size_t)-1) || rb==sizeof(buffer)) | ||
345 | throw napkin::exception("error reading datafile"); | ||
346 | napkin::hypnodata_ptr_t hd = napkin::sleeptracker::decode(buffer,rb); | ||
347 | import_data(hd); | ||
348 | } | ||
349 | } | ||
350 | #endif | ||
351 | |||
352 | #ifndef NDEBUG | ||
353 | void on_debug() { | ||
354 | } | ||
355 | #endif | ||
356 | }; | ||
357 | |||
358 | int main(int argc,char**argv) { | ||
359 | try { | ||
360 | Gtk::Main m(argc,argv); | ||
361 | napkin_ui hui; | ||
362 | m.run(hui); | ||
363 | return 0; | ||
364 | }catch(std::exception& e) { | ||
365 | cerr << "oops: " << e.what() << endl; | ||
366 | } | ||
367 | } | ||