1 #include "mainwindow.h"
2 #include "navigation.h"
3 #include <iostream>
4 #include <fontconfig/fontconfig.h>
5
6 #ifdef HAVE_CLATEXMATH
7 #define __OS_Linux__
8 #include "latex.h"
9 #endif
10
CMainWindow(const Glib::RefPtr<Gtk::Application> & app)11 CMainWindow::CMainWindow(const Glib::RefPtr<Gtk::Application>& app) : Gtk::ApplicationWindow(app), nav_model(), sview()
12 {
13 // Determine paths to operate on.
14 CalculatePaths();
15
16 // Load config.
17 LoadConfig();
18
19 printf("== This is notekit, built at " __TIMESTAMP__ ". ==\n");
20 printf("Detected paths:\n");
21 printf("Config: %s\n",config_path.c_str());
22 printf("Active notes path: %s\n",config["base_path"].asString().c_str());
23 printf("Default notes path: %s\n",default_base_path.c_str());
24 printf("Resource path: %s\n",data_path.c_str());
25 printf("\n");
26
27 #ifdef HAVE_CLATEXMATH
28 tex::LaTeX::init((data_path+"/data/latex").c_str());
29 /* allow \newcommand to override quietly, since we will be rerendering \newcommands unpredictably */
30 tex::TeXRender *r = tex::LaTeX::parse(tex::utf82wide("\\fatalIfCmdConflict{false}"),1,1,1,0);
31 if(r) delete r;
32 #endif
33
34 sview.Init(data_path, config["use_highlight_proxy"].asBool());
35 nav_model.SetBasePath(config["base_path"].asString());
36
37 // make sure document is in place for tree expansion so we can set the selection
38 selected_document = config["active_document"].asString();
39
40 // set up window
41 set_border_width(0);
42 set_default_size(900,600);
43
44 // load additional fonts
45 FcConfigAppFontAddDir(NULL, (FcChar8*)(data_path+"/data/fonts").c_str());
46
47 /* load stylesheet */
48 Glib::RefPtr<Gtk::CssProvider> sview_css = Gtk::CssProvider::create();
49 sview_css->load_from_path(data_path+"/data/stylesheet.css");
50
51 get_style_context()->add_class("notekit");
52 override_background_color(Gdk::RGBA("rgba(0,0,0,0)"));
53 if( GdkVisual *vrgba = gdk_screen_get_rgba_visual( get_screen()->gobj() ) ) {
54 gtk_widget_set_visual(GTK_WIDGET(gobj()), vrgba );
55 }
56
57 /* set up menu */
58 am.prefs.set_label("Preferences");
59 am.prefs.signal_activate().connect( sigc::mem_fun(this,&CMainWindow::RunConfigWindow) );
60 appmenu.append(am.prefs);
61 am.exprt.set_label("Export current");
62 am.exprt.signal_activate().connect( sigc::mem_fun(this,&CMainWindow::FetchAndExport) );
63 appmenu.append(am.exprt);
64 appmenu.append(am.sep);
65 am.hide_sidebar.set_label("Hide sidebar");
66 // this should probably use Gio::Menu actions instead...? documentation is an unholy mess on this
67 am.hide_sidebar.signal_activate().connect( sigc::bind( sigc::mem_fun(this,&CMainWindow::on_action), "toggle-sidebar", WND_ACTION_TOGGLE_SIDEBAR, 1));
68 appmenu.append(am.hide_sidebar);
69
70 am.render_markdown.set_label("Full markdown rendering");
71 am.render_markdown.set_active(true);
72 am.render_markdown.signal_activate().connect( sigc::bind( sigc::mem_fun(this,&CMainWindow::on_action), "toggle-markdown-rendering", WND_ACTION_TOGGLE_MARKDOWN_RENDERING, 1));
73 appmenu.append(am.render_markdown);
74
75 appmenu.show_all();
76
77 /* set up header bar */
78 use_hbar = config["use_headerbar"].asBool();
79
80 if(use_hbar) {
81 hbar.set_show_close_button(true);
82 hbar.set_title("NoteKit");
83 appbutton.set_image_from_icon_name("accessories-text-editor", Gtk::ICON_SIZE_BUTTON, true);
84 appbutton.set_menu(appmenu);
85 set_icon_name("accessories-text-editor");
86
87 hbar.pack_start(appbutton);
88 set_titlebar(hbar);
89 }
90
91 /* install tree view */
92 nav_model.AttachView(this,&nav);
93 nav.get_style_context()->add_class("sidebar");
94 nav.get_style_context()->add_class("nemo-window");
95
96 nav_scroll.add(nav);
97 nav_scroll.set_size_request(200,-1);
98 split.pack_start(nav_scroll,Gtk::PACK_SHRINK);
99
100 get_style_context()->add_provider(sview_css,GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
101 nav.get_style_context()->add_provider(sview_css,GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
102
103 /* load cursor types for document */
104 pen_cursor = Gdk::Cursor::create(Gdk::Display::get_default(),"default");
105 text_cursor = Gdk::Cursor::create(Gdk::Display::get_default(),"text");
106 eraser_cursor = Gdk::Cursor::create(Gdk::Display::get_default(),"cell");
107 selection_cursor = Gdk::Cursor::create(Gdk::Display::get_default(),"cross");
108
109 /* install document view */
110 sbuffer = sview.get_source_buffer();
111 sview.get_style_context()->add_provider(sview_css,GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
112 sview.set_name("mainView");
113 sview_scroll.get_style_context()->add_provider(sview_css,GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
114 sview_scroll.set_name("mainViewScroll");
115 //sview.set_border_window_size(Gtk::TEXT_WINDOW_LEFT,16);
116
117 sview_scroll.add( sview);
118 sview.margin_x=32;
119 sview.property_left_margin().set_value(32);
120 split.pack_start( sview_scroll );
121
122 /* install tool palette */
123 InitToolbar();
124 // some actions need data from the main window and therefore need to live here
125 insert_action_group("notebook",sview.actions);
126 UpdateToolbarColors();
127 split.pack_start(*toolbar,Gtk::PACK_SHRINK);
128 on_action("color1",WND_ACTION_COLOR,1);
129
130 #define ACTION(name,param1,param2) sview.actions->add_action(name, sigc::bind( sigc::mem_fun(this,&CMainWindow::on_action), std::string(name), param1, param2 ) )
131 ACTION("next-note",WND_ACTION_NEXT_NOTE,1);
132 ACTION("prev-note",WND_ACTION_PREV_NOTE,1);
133
134 /* add the top-level layout */
135 if(use_hbar) {
136 add(split);
137 } else {
138 /* without a headerbar, we also need to have a vbox for the classic menu here */
139 cm.view.set_label("View");
140 // TODO: this results in Gtk-CRITICALs on quit as appmenu elements are double freed.
141 // Is there any way we can set a submenu here without making free of cm.view recursively free appmenu?
142 cm.view.set_submenu(appmenu);
143 cm.mbar.append(cm.view);
144 cm.menu_box.pack_start(cm.mbar,Gtk::PACK_SHRINK);
145 cm.menu_box.pack_start(split);
146 add(cm.menu_box);
147 }
148
149 show_all();
150
151 /* workaround to make sure the right pen width is selected at start */
152 Gtk::RadioToolButton *b;
153 toolbar_builder->get_widget("medium",b); b->set_active(false); b->set_active(true);
154
155 OpenDocument(selected_document);
156
157 close_handler = this->signal_delete_event().connect( sigc::mem_fun(this, &CMainWindow::on_close) );
158
159 signal_motion_notify_event().connect(sigc::mem_fun(this,&CMainWindow::on_motion_notify),false);
160
161 idle_timer = Glib::signal_timeout().connect(sigc::mem_fun(this,&CMainWindow::on_idle),5000,Glib::PRIORITY_LOW);
162 }
163
mkdirp(std::string dir)164 int mkdirp(std::string dir)
165 {
166 Glib::RefPtr<Gio::File> f = Gio::File::create_for_path(dir);
167 try {
168 return f->make_directory_with_parents();
169 } catch(Gio::Error &e) {
170 return false;
171 }
172 }
173
174 /* Half-baked interpretation of XDG spec */
CalculatePaths()175 void CMainWindow::CalculatePaths()
176 {
177 char *dbg = getenv("NK_DEVEL");
178 if(dbg != NULL) {
179 fprintf(stderr,"INFO: Debug mode set! Will operate in %s.\n", dbg);
180 data_path=dbg;
181 config_path= data_path + "/notesbase";
182 default_base_path= data_path + "/notesbase";
183 return;
184 }
185
186 char *home = getenv("HOME");
187 if(!home || !*home) {
188 fprintf(stderr,"WARNING: Could not determine user's home directory! Will operate in current working directory.\n");
189 config_path="notesbase";
190 default_base_path="notesbase";
191 data_path=".";
192 return;
193 }
194
195 char *config_home = getenv("XDG_CONFIG_HOME");
196 if(!config_home || !*config_home) config_path=std::string(home)+"/.config/notekit";
197 else config_path=std::string(config_home)+"/notekit";
198
199 if(mkdirp(config_path)) {
200 fprintf(stderr,"WARNING: Could not create config path '%s'. Falling back to current working directory.\n",config_path.c_str());
201 config_path="notesbase";
202 }
203
204 char *data_home = getenv("XDG_DATA_HOME");
205 if(!data_home || !*data_home) default_base_path=std::string(home)+"/.local/share/notekit";
206 else default_base_path=std::string(data_home)+"/notekit";
207
208 /* TODO: we might not need this if we have a manually-set base path already */
209 if(mkdirp(default_base_path)) {
210 fprintf(stderr,"WARNING: Could not create default base path '%s'. Falling back to current working directory.\n",default_base_path.c_str());
211 default_base_path="notesbase";
212 }
213
214 char *data_dirs = getenv("XDG_DATA_DIRS");
215 if(!data_dirs || !*data_dirs)
216 data_dirs=strdup("/usr/local/share:/usr/share");
217 else
218 data_dirs=strdup(data_dirs);
219
220 char *next=data_dirs;
221 strtok(data_dirs,":");
222 struct stat statbuf;
223 data_path=".";
224 do {
225 std::string putative_dir = std::string(next) + "/notekit";
226 if(!stat(putative_dir.c_str(),&statbuf)) {
227 data_path=putative_dir;
228 break;
229 }
230 } while( (next = strtok(NULL,":")) );
231 free(data_dirs);
232 }
233
LoadConfig()234 void CMainWindow::LoadConfig()
235 {
236 char *buf; gsize length;
237 Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(config_path+"/config.json");
238
239 try {
240 file->load_contents(buf, length);
241 } catch(Gio::Error &e) {
242 file = Gio::File::create_for_path(data_path+"/data/default_config.json");
243
244 try {
245 file->load_contents(buf, length);
246 } catch(Gio::Error &e) {
247 fprintf(stderr,"FATAL: config.json not found in note tree, and default_config.json not found in data tree!\n");
248 exit(-1);
249 }
250 }
251
252 Json::CharReaderBuilder rbuilder;
253 std::istringstream i(buf);
254 std::string errs;
255 Json::parseFromStream(rbuilder, i, &config, &errs);
256
257 /* set default base path if none has been explicitly set */
258 if(!config["base_path"].isString() || config["base_path"].asString()=="") {
259 config["base_path"]=default_base_path;
260 }
261
262 g_free(buf);
263 }
264
SaveConfig()265 void CMainWindow::SaveConfig()
266 {
267 Json::StreamWriterBuilder wbuilder;
268 std::string str = Json::writeString(wbuilder, config);
269
270 FILE *fl = fopen( (config_path+"/config.json").c_str(), "wb");
271 fwrite(str.c_str(),str.length(),1,fl);
272 fclose(fl);
273 }
274
RunConfigWindow()275 void CMainWindow::RunConfigWindow()
276 {
277 Glib::RefPtr<Gtk::Builder> config_builder;
278
279 config_builder = Gtk::Builder::create_from_file(data_path+"/data/preferences.glade");
280 Gtk::Dialog *dlg;
281 config_builder->get_widget("preferences",dlg);
282 Gtk::FileChooserButton *dir;
283 Gtk::CheckButton *use_headerbar, *use_highlight_proxy;
284 config_builder->get_widget("base_path",dir);
285 config_builder->get_widget("use_headerbar",use_headerbar);
286 config_builder->get_widget("use_highlight_proxy",use_highlight_proxy);
287 dir->set_current_folder(config["base_path"].asString());
288 use_headerbar->set_active(config["use_headerbar"].asBool());
289 use_highlight_proxy->set_active(config["use_highlight_proxy"].asBool());
290 if(dlg->run() == Gtk::RESPONSE_OK) {
291 config["base_path"]=dir->get_current_folder();
292 config["use_highlight_proxy"]=use_highlight_proxy->get_active();
293 config["use_headerbar"]=use_headerbar->get_active();
294 SaveConfig();
295 }
296 dlg->hide();
297 }
298
InitToolbar()299 void CMainWindow::InitToolbar()
300 {
301 toolbar_builder = Gtk::Builder::create_from_file(data_path+"/data/toolbar.glade");
302 toolbar_builder->get_widget("toolbar",toolbar);
303
304 Glib::RefPtr<Gtk::IconTheme> thm = Gtk::IconTheme::get_default(); //get_for_screen(get_window()->get_screen());
305 thm->append_search_path(data_path+"/data/icons");
306
307 toolbar_style = Gtk::CssProvider::create();
308 toolbar_style->load_from_data("#color1 { -gtk-icon-palette: warning #ff00ff; }");
309
310 /* generate colour buttons */
311 Gtk::RadioToolButton *b0 = nullptr;
312 for(unsigned int i=1;i<=config["colors"].size();++i) {
313 char buf[255];
314 //Gtk::Widget *sdfg;
315
316 sprintf(buf,"color%d",i);
317
318 #define ACTION(name,param1,param2) sview.actions->add_action(name, sigc::bind( sigc::mem_fun(this,&CMainWindow::on_action), std::string(name), param1, param2 ) )
319 ACTION(buf,WND_ACTION_COLOR,i);
320
321 Gtk::RadioToolButton *b = nullptr;
322 if(i == 1) {
323 b = b0 = new Gtk::RadioToolButton(buf);
324 } else {
325 Gtk::RadioToolButton::Group g = b0->get_group(); // because for some reason, the group argument is &
326 b = new Gtk::RadioToolButton(g, buf);
327 }
328 b->set_icon_name("pick-color-symbolic");
329 b->set_name(buf);
330 b->signal_button_press_event().connect(sigc::bind(sigc::mem_fun(this,&CMainWindow::on_click_color),i));
331 b->get_style_context()->add_provider(toolbar_style,GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
332 sprintf(buf,"notebook.color%d",i);
333 gtk_actionable_set_action_name(GTK_ACTIONABLE(b->gobj()), buf);
334
335 Gtk::manage(b);
336 toolbar->append(*b);
337 }
338 }
339
GetColor(int id,float & r,float & g,float & b)340 void CMainWindow::GetColor(int id, float &r, float &g, float &b)
341 {
342 Json::Value def;
343 def["r"]=def["g"]=def["b"]=0;
344 r=config["colors"].get(id-1,def)["r"].asFloat();
345 g=config["colors"].get(id-1,def)["g"].asFloat();
346 b=config["colors"].get(id-1,def)["b"].asFloat();
347 }
348
SetColor(int id,float r,float g,float b)349 void CMainWindow::SetColor(int id, float r, float g, float b)
350 {
351 Json::Value def;
352 def["r"]=def["g"]=def["b"]=0;
353 config["colors"][id-1]["r"]=r;
354 config["colors"][id-1]["g"]=g;
355 config["colors"][id-1]["b"]=b;
356 }
357
358
UpdateToolbarColors()359 void CMainWindow::UpdateToolbarColors()
360 {
361 std::string colorcss;
362
363 int i=1;
364 char buf[255];
365
366 for(auto a : config["colors"]) {
367 sprintf(buf,"#color%d { -gtk-icon-palette: warning rgb(%d,%d,%d); }\n", i, (int)(255*a["r"].asDouble()), (int)(255*a["g"].asDouble()), (int)(255*a["b"].asDouble()) );
368 colorcss += buf;
369 ++i;
370 }
371
372 toolbar_style->load_from_data(colorcss);
373 }
374
HardClose()375 void CMainWindow::HardClose()
376 {
377 close_handler.disconnect();
378 close();
379 }
380
FocusDocument()381 void CMainWindow::FocusDocument()
382 {
383 sview.grab_focus();
384 }
385
FetchAndSave()386 void CMainWindow::FetchAndSave()
387 {
388 sview.last_modified=0;
389
390 if(active_document=="") return;
391
392 gsize len;
393 guint8 *buf;
394 buf = sbuffer->serialize(sbuffer,"text/notekit-markdown",sbuffer->begin(),sbuffer->end(),len);
395 std::string str((const char*)buf,len);
396 g_free(buf);
397 //Glib::ustring str = sbuffer->get_text(true);
398
399 std::string filename;
400 filename = nav_model.base;
401 filename += "/";
402 filename += active_document;
403 std::string tmp_filename;
404 tmp_filename = filename;
405 tmp_filename += ".tmp";
406
407 /* TODO: this kills the xattrs */
408 FILE *fl = fopen(tmp_filename.c_str(), "wb");
409 fwrite(str.c_str(),str.length(),1,fl);
410 fclose(fl);
411
412 /* atomic replace */
413 rename(tmp_filename.c_str(), filename.c_str());
414 }
415
FetchAndExport()416 void CMainWindow::FetchAndExport()
417 {
418 if(active_document=="") return;
419
420 Gtk::FileChooserDialog d("Export current note", Gtk::FILE_CHOOSER_ACTION_SAVE);
421 d.set_transient_for(*this);
422 d.add_button("_Cancel", Gtk::RESPONSE_CANCEL);
423 d.add_button("_Export", Gtk::RESPONSE_OK);
424
425 auto filter_md = Gtk::FileFilter::create();
426 filter_md->set_name("Portable markdown");
427 filter_md->add_mime_type("text/plain");
428 filter_md->add_mime_type("text/markdown");
429 d.add_filter(filter_md);
430
431 auto filter_pdf = Gtk::FileFilter::create();
432 filter_pdf->set_name("Pandoc to PDF (requires pandoc)");
433 filter_pdf->add_mime_type("application/pdf");
434 d.add_filter(filter_pdf);
435
436 switch(d.run()) {
437 case Gtk::RESPONSE_OK: {
438 gsize len;
439 guint8 *buf;
440 buf = sbuffer->serialize(sbuffer,"text/plain",sbuffer->begin(),sbuffer->end(),len);
441 std::string str((const char*)buf,len);
442 g_free(buf);
443
444 if(d.get_filter() == filter_md) {
445 FILE *fl = fopen(d.get_filename().c_str(), "wb");
446 fwrite(str.c_str(),str.length(),1,fl);
447 fclose(fl);
448 } else if(d.get_filter() == filter_pdf) {
449 char *tempmd = tempnam(NULL,"nkexport");
450 FILE *fl = fopen(tempmd, "wb");
451 fwrite(str.c_str(),str.length(),1,fl);
452 fclose(fl);
453
454 char cmdbuf[1024];
455 snprintf(cmdbuf,1024,"pandoc -f markdown+hard_line_breaks+compact_definition_lists -t latex -o \"%s\" \"%s\"",d.get_filename().c_str(),tempmd);
456 int retval;
457 if(retval=system(cmdbuf)) {
458 printf("Exporting to PDF (temporary file: %s): failure (%d). Temporary file not deleted.\n",tempmd,retval);
459 } else {
460 printf("Exporting to PDF (temporary file: %s): success.\n",tempmd);
461 ::remove(tempmd);
462 }
463 }
464
465 break; }
466 default:;
467 }
468 }
469
OpenDocument(std::string filename)470 void CMainWindow::OpenDocument(std::string filename)
471 {
472 FetchAndSave();
473
474 if(filename=="") {
475 sview.set_editable(false);
476 sview.set_can_focus(false);
477
478 SetActiveFilename("");
479
480 sbuffer->begin_not_undoable_action();
481 sbuffer->set_text("( Nothing opened. Please create or open a file. ) ");
482 sbuffer->end_not_undoable_action();
483 return;
484 }
485 sview.set_editable(true);
486 sview.set_can_focus(true);
487
488 char *buf; gsize length;
489 try {
490 Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(nav_model.base + "/" + filename);
491 file->load_contents(buf, length);
492
493 sbuffer->begin_not_undoable_action();
494 sbuffer->set_text("");
495
496 if(file->has_parent()) {
497 sview.SetDocumentPath(file->get_parent()->get_path());
498 } else {
499 sview.SetDocumentPath("");
500 }
501
502 Gtk::TextBuffer::iterator i = sbuffer->begin();
503 if(length)
504 sbuffer->deserialize(sbuffer,"text/notekit-markdown",i,(guint8*)buf,length);
505 sbuffer->end_not_undoable_action();
506
507 SetActiveFilename(filename);
508
509 g_free(buf);
510 } catch(Gio::Error &e) {
511 sview.set_editable(false);
512 sview.set_can_focus(false);
513
514 fprintf(stderr,"Error: Failed to load document %s!\n",filename.c_str());
515 SetActiveFilename("");
516
517 sbuffer->begin_not_undoable_action();
518 char error_msg[512];
519 sprintf(error_msg,"( Failed to open %s. Please create or open a file. ) ",filename.c_str());
520 sbuffer->set_text(error_msg);
521 sbuffer->end_not_undoable_action();
522
523 return;
524 }
525 }
526
527 /* set apparent opened document filename without actually loading/unload anything */
SetActiveFilename(std::string filename)528 void CMainWindow::SetActiveFilename(std::string filename)
529 {
530 active_document = filename;
531 selected_document = filename;
532 if(use_hbar) {
533 hbar.set_subtitle(active_document);
534 } else {
535 set_title(active_document + " - NoteKit");
536 }
537 config["active_document"]=filename;
538 }
539
~CMainWindow()540 CMainWindow::~CMainWindow()
541 {
542 }
543
on_close(GdkEventAny * any_event)544 bool CMainWindow::on_close(GdkEventAny* any_event)
545 {
546 SaveConfig();
547
548 if(active_document=="") return false;
549
550 FetchAndSave();
551
552 return false;
553 }
554
on_action(std::string name,int type,int param)555 void CMainWindow::on_action(std::string name, int type, int param)
556 {
557 printf("%s\n",name.c_str());
558 switch(type) {
559 case WND_ACTION_COLOR:
560 GetColor(param, sview.active.r,sview.active.g, sview.active.b);
561 sview.active.a=1;
562 break;
563 case WND_ACTION_NEXT_NOTE:
564 nav_model.NextDoc();
565 break;
566 case WND_ACTION_PREV_NOTE:
567 nav_model.PrevDoc();
568 break;
569 case WND_ACTION_TOGGLE_SIDEBAR:
570 if(am.hide_sidebar.get_active()) {
571 nav_scroll.set_visible(false);
572 } else {
573 nav_scroll.set_visible(true);
574 }
575 break;
576 case WND_ACTION_TOGGLE_MARKDOWN_RENDERING:
577 if(am.render_markdown.get_active()) {
578 sview.EnableProximityRendering();
579 } else {
580 sview.DisableProximityRendering();
581 }
582 break;
583 }
584 }
585
on_idle()586 bool CMainWindow::on_idle()
587 {
588 // autosave if document has been modified and nothing has been typed for 5 seconds
589 if(sview.last_modified > 0 && g_get_real_time()-sview.last_modified > 5000000LL) {
590 printf("autosave\n");
591 FetchAndSave();
592 }
593
594 return true;
595 }
596
on_click_color(GdkEventButton * e,int number)597 bool CMainWindow::on_click_color(GdkEventButton* e, int number)
598 {
599 if(e->button == 3) {
600 // right click
601 Gtk::ColorChooserDialog dialog("Select a color");
602 dialog.set_transient_for(*this);
603
604 char buf[32];
605 float r,g,b;
606 GetColor(number,r,g,b);
607 sprintf(buf,"rgb(%d,%d,%d)",(int)(r*255),(int)(g*255),(int)(b*255));
608
609 dialog.set_rgba(Gdk::RGBA(buf));
610
611 const int result = dialog.run();
612
613 switch(result) {
614 case Gtk::RESPONSE_OK: {
615 Gdk::RGBA col = dialog.get_rgba();
616 SetColor(number,col.get_red(),col.get_green(),col.get_blue());
617 UpdateToolbarColors();
618 break;
619 }
620 default:;
621 }
622
623 return true;
624 }
625 return false;
626 }
627
on_motion_notify(GdkEventMotion * e)628 bool CMainWindow::on_motion_notify(GdkEventMotion *e)
629 {
630 GdkDevice *d = gdk_event_get_source_device((GdkEvent*)e);
631
632 bool device_switched=false;
633
634 if(d != sview.last_device) {
635 sview.last_device = d;
636 device_switched=true;
637
638 Gtk::RadioToolButton *b = nullptr;
639
640 if(!sview.devicemodes.count(d)) {
641 if(gdk_device_get_n_axes(d)>4) {
642 // assume it's a pen
643 sview.devicemodes[d] = NB_MODE_DRAW;
644 } else {
645 sview.devicemodes[d] = NB_MODE_TEXT;
646 }
647 }
648
649 switch(sview.devicemodes[d]) {
650 case NB_MODE_DRAW:
651 toolbar_builder->get_widget("draw",b);
652 break;
653 case NB_MODE_TEXT:
654 toolbar_builder->get_widget("text",b);
655 break;
656 case NB_MODE_ERASE:
657 toolbar_builder->get_widget("erase",b);
658 break;
659 case NB_MODE_SELECT:
660 toolbar_builder->get_widget("select",b);
661 break;
662 }
663 b->set_active();
664 }
665
666 if(device_switched || sview.update_cursor) {
667 sview.update_cursor=false;
668
669 switch(sview.devicemodes[d]) {
670 case NB_MODE_DRAW:
671 sview.SetCursor(pen_cursor);
672 sview.set_cursor_visible(false);
673 break;
674 case NB_MODE_TEXT:
675 sview.SetCursor(text_cursor);
676 sview.set_cursor_visible(true);
677 break;
678 case NB_MODE_ERASE:
679 sview.SetCursor(eraser_cursor);
680 sview.set_cursor_visible(false);
681 break;
682 case NB_MODE_SELECT:
683 sview.SetCursor(selection_cursor);
684 sview.set_cursor_visible(false);
685 break;
686 }
687 }
688
689 return false;
690 }
691