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