1 /*
2  * Copyright (C) 1999-2019 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
4  * Copyright (C) 2006-2007 Tim Mayberry <mojofunk@gmail.com>
5  * Copyright (C) 2007-2011 David Robillard <d@drobilla.net>
6  * Copyright (C) 2007-2011 Doug McLain <doug@nostar.net>
7  * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
8  * Copyright (C) 2013 John Emmas <john@creativepost.co.uk>
9  * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License along
22  * with this program; if not, write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25 
26 #include <cmath>
27 #include <fcntl.h>
28 #include <signal.h>
29 #include <unistd.h>
30 #include <cerrno>
31 #include <climits>
32 #include <cctype>
33 
34 #include <gtkmm.h>
35 
36 #include "pbd/error.h"
37 #include "pbd/touchable.h"
38 #include "pbd/failed_constructor.h"
39 #include "pbd/localtime_r.h"
40 #include "pbd/pthread_utils.h"
41 #include "pbd/replace_all.h"
42 
43 #include "gtkmm2ext/application.h"
44 #include "gtkmm2ext/bindings.h"
45 #include "gtkmm2ext/gtk_ui.h"
46 #include "gtkmm2ext/textviewer.h"
47 #include "gtkmm2ext/utils.h"
48 #include "gtkmm2ext/window_title.h"
49 #include "gtkmm2ext/actions.h"
50 #include "gtkmm2ext/activatable.h"
51 #include "gtkmm2ext/actions.h"
52 #include "gtkmm2ext/gui_thread.h"
53 
54 #include "pbd/i18n.h"
55 
56 using namespace Gtkmm2ext;
57 using namespace Gtk;
58 using namespace Glib;
59 using namespace PBD;
60 using std::map;
61 
62 UI*   UI::theGtkUI = 0;
63 
64 BaseUI::RequestType Gtkmm2ext::NullMessage = BaseUI::new_request_type();
65 BaseUI::RequestType Gtkmm2ext::ErrorMessage = BaseUI::new_request_type();
66 BaseUI::RequestType Gtkmm2ext::TouchDisplay = BaseUI::new_request_type();
67 BaseUI::RequestType Gtkmm2ext::StateChange = BaseUI::new_request_type();
68 BaseUI::RequestType Gtkmm2ext::SetTip = BaseUI::new_request_type();
69 BaseUI::RequestType Gtkmm2ext::AddIdle = BaseUI::new_request_type();
70 BaseUI::RequestType Gtkmm2ext::AddTimeout = BaseUI::new_request_type();
71 
72 #include "pbd/abstract_ui.cc"  /* instantiate the template */
73 
74 template class AbstractUI<Gtkmm2ext::UIRequest>;
75 
UI(string application_name,string thread_name,int * argc,char *** argv)76 UI::UI (string application_name, string thread_name, int *argc, char ***argv)
77 	: AbstractUI<UIRequest> (thread_name)
78 	, _receiver (*this)
79 	, global_bindings (0)
80 	, errors (0)
81 {
82 	theMain = new Main (argc, argv);
83 
84 	char buf[18];
85 	/* pthread public name has a 16 char limit */
86 	snprintf (buf, sizeof (buf), "%.11sGUI", PROGRAM_NAME);
87 	pthread_set_name (buf);
88 
89 	_active = false;
90 
91 	if (!theGtkUI) {
92 		theGtkUI = this;
93 	} else {
94 		fatal << "duplicate UI requested" << endmsg;
95 		abort(); /* NOTREACHED */
96 	}
97 
98 	/* the GUI event loop runs in the main thread of the app,
99 	   which is assumed to have called this.
100 	*/
101 
102 	run_loop_thread = Threads::Thread::self();
103 
104 	/* store "this" as the UI-for-thread of this thread, same argument
105 	   as for previous line.
106 	*/
107 
108 	set_event_loop_for_thread (this);
109 
110 	/* we will be receiving requests */
111 
112 	EventLoop::register_request_buffer_factory ("gui", request_buffer_factory);
113 
114 	/* attach our request source to the default main context */
115 
116 	attach_request_source ();
117 
118 	errors = new TextViewer (800,600);
119 	errors->text().set_editable (false);
120 	errors->text().set_name ("ErrorText");
121 	errors->signal_unmap().connect (sigc::bind (sigc::ptr_fun (&ActionManager::uncheck_toggleaction), X_("Editor/toggle-log-window")));
122 
123 	Glib::set_application_name (application_name);
124 
125 	WindowTitle title(Glib::get_application_name());
126 	title += _("Log");
127 	errors->set_title (title.get_string());
128 
129 	errors->dismiss_button().set_name ("ErrorLogCloseButton");
130 	errors->signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), (Window *) errors));
131 	errors->set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
132 
133 	//load_rcfile (rcfile);
134 
135 	/* instantiate the Application singleton */
136 
137 	Application::instance();
138 }
139 
~UI()140 UI::~UI ()
141 {
142 	_receiver.hangup ();
143 	delete (errors);
144 }
145 
146 bool
caller_is_ui_thread()147 UI::caller_is_ui_thread ()
148 {
149 	return Threads::Thread::self() == run_loop_thread;
150 }
151 
152 int
load_rcfile(string path,bool themechange)153 UI::load_rcfile (string path, bool themechange)
154 {
155 	/* Yes, pointers to Glib::RefPtr.  If these are not kept around,
156 	 * a segfault somewhere deep in the wonderfully robust glib will result.
157 	 * This does not occur if wiget.get_style is used instead of rc.get_style below,
158 	 * except that doesn't actually work...
159 	 */
160 
161 	static Glib::RefPtr<Style>* fatal_style   = 0;
162 	static Glib::RefPtr<Style>* error_style   = 0;
163 	static Glib::RefPtr<Style>* warning_style = 0;
164 	static Glib::RefPtr<Style>* info_style    = 0;
165 	static Glib::RefPtr<Style>* debug_style    = 0;
166 
167 	if (path.length() == 0) {
168 		return -1;
169 	}
170 
171 	if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
172 		error << "UI: couldn't find rc file \""
173 		      << path
174 		      << '"'
175 		      << endmsg;
176 		return -1;
177 	}
178 
179 	RC rc (path.c_str());
180 	//this is buggy in gtkmm for some reason, so use C
181 	//RC::reset_styles (Gtk::Settings::get_default());
182 	gtk_rc_reset_styles (gtk_settings_get_default());
183 
184 	theme_changed.emit();
185 
186 	if (themechange) {
187 		return 0; //Don't continue on every time there is a theme change
188 	}
189 
190 	/* have to pack widgets into a toplevel window so that styles will stick */
191 
192 	Window temp_window (WINDOW_TOPLEVEL);
193 	temp_window.ensure_style ();
194 
195 	HBox box;
196 	Label fatal_widget;
197 	Label error_widget;
198 	Label warning_widget;
199 	Label info_widget;
200 	Label debug_widget;
201 	RefPtr<Gtk::Style> style;
202 	RefPtr<TextBuffer> buffer (errors->text().get_buffer());
203 
204 	box.pack_start (fatal_widget);
205 	box.pack_start (error_widget);
206 	box.pack_start (warning_widget);
207 	box.pack_start (info_widget);
208 	box.pack_start (debug_widget);
209 
210 	fatal_ptag = buffer->create_tag();
211 	fatal_mtag = buffer->create_tag();
212 	error_ptag = buffer->create_tag();
213 	error_mtag = buffer->create_tag();
214 	warning_ptag = buffer->create_tag();
215 	warning_mtag = buffer->create_tag();
216 	info_ptag = buffer->create_tag();
217 	info_mtag = buffer->create_tag();
218 	debug_ptag = buffer->create_tag();
219 	debug_mtag = buffer->create_tag();
220 
221 	fatal_widget.set_name ("FatalMessage");
222 	delete fatal_style;
223 
224 	/* This next line and the similar ones below are sketchily
225 	 * guessed to fix #2885.  I think maybe that problems occur
226 	 * because with gtk_rc_get_style (to quote its docs) "no
227 	 * refcount is added to the returned style".  So I've switched
228 	 * this to use Glib::wrap with take_copy == true, which requires
229 	 * all the nasty casts and calls to plain-old-C GTK.
230 	 *
231 	 * At worst I think this causes a memory leak; at least it appears
232 	 * to fix the bug.
233 	 *
234 	 * I could be wrong about any or all of the above.
235 	 */
236 	fatal_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (fatal_widget.gobj())), true));
237 
238 	fatal_ptag->property_font_desc().set_value((*fatal_style)->get_font());
239 	fatal_ptag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_ACTIVE));
240 	fatal_ptag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_ACTIVE));
241 	fatal_mtag->property_font_desc().set_value((*fatal_style)->get_font());
242 	fatal_mtag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_NORMAL));
243 	fatal_mtag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_NORMAL));
244 
245 	error_widget.set_name ("ErrorMessage");
246 	delete error_style;
247 	error_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (error_widget.gobj())), true));
248 
249 	error_ptag->property_font_desc().set_value((*error_style)->get_font());
250 	error_ptag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_ACTIVE));
251 	error_ptag->property_background_gdk().set_value((*error_style)->get_bg(STATE_ACTIVE));
252 	error_mtag->property_font_desc().set_value((*error_style)->get_font());
253 	error_mtag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_NORMAL));
254 	error_mtag->property_background_gdk().set_value((*error_style)->get_bg(STATE_NORMAL));
255 
256 	warning_widget.set_name ("WarningMessage");
257 	delete warning_style;
258 	warning_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (warning_widget.gobj())), true));
259 
260 	warning_ptag->property_font_desc().set_value((*warning_style)->get_font());
261 	warning_ptag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_ACTIVE));
262 	warning_ptag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_ACTIVE));
263 	warning_mtag->property_font_desc().set_value((*warning_style)->get_font());
264 	warning_mtag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_NORMAL));
265 	warning_mtag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_NORMAL));
266 
267 	info_widget.set_name ("InfoMessage");
268 	delete info_style;
269 	info_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (info_widget.gobj())), true));
270 
271 	info_ptag->property_font_desc().set_value((*info_style)->get_font());
272 	info_ptag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_ACTIVE));
273 	info_ptag->property_background_gdk().set_value((*info_style)->get_bg(STATE_ACTIVE));
274 	info_mtag->property_font_desc().set_value((*info_style)->get_font());
275 	info_mtag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_NORMAL));
276 	info_mtag->property_background_gdk().set_value((*info_style)->get_bg(STATE_NORMAL));
277 
278 	debug_widget.set_name ("DebugMessage");
279 	delete debug_style;
280 	debug_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (debug_widget.gobj())), true));
281 
282 	debug_ptag->property_font_desc().set_value((*debug_style)->get_font());
283 	debug_ptag->property_foreground_gdk().set_value((*debug_style)->get_fg(STATE_ACTIVE));
284 	debug_ptag->property_background_gdk().set_value((*debug_style)->get_bg(STATE_ACTIVE));
285 	debug_mtag->property_font_desc().set_value((*debug_style)->get_font());
286 	debug_mtag->property_foreground_gdk().set_value((*debug_style)->get_fg(STATE_NORMAL));
287 	debug_mtag->property_background_gdk().set_value((*debug_style)->get_bg(STATE_NORMAL));
288 
289 	return 0;
290 }
291 
292 void
run(Receiver & old_receiver)293 UI::run (Receiver &old_receiver)
294 {
295 	_receiver.listen_to (debug);
296 	_receiver.listen_to (info);
297 	_receiver.listen_to (warning);
298 	_receiver.listen_to (error);
299 	_receiver.listen_to (fatal);
300 
301 	/* stop the old receiver (text/console) once we hit the first idle */
302 
303 	Glib::signal_idle().connect (bind_return (mem_fun (old_receiver, &Receiver::hangup), false));
304 
305 	if (starting ()) {
306 		return;
307 	}
308 
309 	_active = true;
310 	theMain->run ();
311 	_active = false;
312 
313 	return;
314 }
315 
316 bool
running()317 UI::running ()
318 {
319 	return _active;
320 }
321 
322 void
quit()323 UI::quit ()
324 {
325 	UIRequest *req = get_request (Quit);
326 
327 	if (req == 0) {
328 		return;
329 	}
330 
331 	send_request (req);
332 }
333 
idle_quit()334 static bool idle_quit ()
335 {
336 	Main::quit ();
337 	return true;
338 }
339 
340 void
do_quit()341 UI::do_quit ()
342 {
343 	if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
344 		Main::quit ();
345 	} else {
346 		Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
347 	}
348 }
349 
350 void
touch_display(Touchable * display)351 UI::touch_display (Touchable *display)
352 {
353 	UIRequest *req = get_request (TouchDisplay);
354 
355 	if (req == 0) {
356 		return;
357 	}
358 
359 	req->display = display;
360 
361 	send_request (req);
362 }
363 
364 void
set_tip(Widget & w,const gchar * tip)365 UI::set_tip (Widget &w, const gchar *tip)
366 {
367 	set_tip(&w, tip, "");
368 }
369 
370 void
set_tip(Widget & w,const std::string & tip)371 UI::set_tip (Widget &w, const std::string& tip)
372 {
373 	set_tip(&w, tip.c_str(), "");
374 }
375 
376 void
set_tip(Widget * w,const gchar * tip,const gchar * hlp)377 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
378 {
379 	UIRequest *req = get_request (SetTip);
380 
381 	std::string msg(tip);
382 
383 	Glib::RefPtr<Gtk::Action> action = w->get_action();
384 
385 	if (!action) {
386 		Gtkmm2ext::Activatable* activatable;
387 		if ((activatable = dynamic_cast<Gtkmm2ext::Activatable*>(w))) {
388 			action = activatable->get_related_action();
389 		}
390 	}
391 
392 	if (action) {
393 		Bindings* bindings = (Bindings*) w->get_data ("ardour-bindings");
394 		if (!bindings) {
395 			Gtk::Window* win = (Gtk::Window*) w->get_toplevel();
396 			if (win) {
397 				bindings = (Bindings*) win->get_data ("ardour-bindings");
398 			}
399 		}
400 
401 		if (!bindings) {
402 			bindings = global_bindings;
403 		}
404 
405 		if (bindings) {
406 			Bindings::Operation op;
407 			KeyboardKey kb = bindings->get_binding_for_action (action, op);
408 			string shortcut = kb.display_label ();
409 			if (!shortcut.empty()) {
410 				replace_all (shortcut, "<", "");
411 				replace_all (shortcut, ">", "-");
412 				msg.append(_("\n\nShortcut: ")).append (shortcut);
413 			}
414 		}
415 	}
416 
417 	if (req == 0) {
418 		return;
419 	}
420 
421 
422 	req->widget = w;
423 	req->msg = msg.c_str();
424 	req->msg2 = hlp;
425 
426 	send_request (req);
427 }
428 
429 void
set_state(Widget * w,StateType state)430 UI::set_state (Widget *w, StateType state)
431 {
432 	UIRequest *req = get_request (StateChange);
433 
434 	if (req == 0) {
435 		return;
436 	}
437 
438 	req->new_state = state;
439 	req->widget = w;
440 
441 	send_request (req);
442 }
443 
444 void
idle_add(int (* func)(void *),void * arg)445 UI::idle_add (int (*func)(void *), void *arg)
446 {
447 	UIRequest *req = get_request (AddIdle);
448 
449 	if (req == 0) {
450 		return;
451 	}
452 
453 	req->function = func;
454 	req->arg = arg;
455 
456 	send_request (req);
457 }
458 
459 /* END abstract_ui interfaces */
460 
461 /** Create a PBD::EventLoop::InvalidationRecord and attach a callback
462  *  to a given sigc::trackable so that PBD::EventLoop::invalidate_request
463  *  is called when that trackable is destroyed.
464  */
465 PBD::EventLoop::InvalidationRecord*
__invalidator(sigc::trackable & trackable,const char * file,int line)466 __invalidator (sigc::trackable& trackable, const char* file, int line)
467 {
468         PBD::EventLoop::InvalidationRecord* ir = new PBD::EventLoop::InvalidationRecord;
469 
470         ir->file = file;
471         ir->line = line;
472 
473         trackable.add_destroy_notify_callback (ir, PBD::EventLoop::invalidate_request);
474 
475         return ir;
476 }
477 
478 void
do_request(UIRequest * req)479 UI::do_request (UIRequest* req)
480 {
481 	if (req->type == ErrorMessage) {
482 
483 		process_error_message (req->chn, req->msg);
484 		free (const_cast<char*>(req->msg)); /* it was strdup'ed */
485 		req->msg = 0; /* don't free it again in the destructor */
486 
487 	} else if (req->type == Quit) {
488 
489 		do_quit ();
490 
491 	} else if (req->type == CallSlot) {
492 #ifndef NDEBUG
493 		if (getenv ("DEBUG_THREADED_SIGNALS")) {
494 			cerr << "call slot for " << event_loop_name() << endl;
495 		}
496 #endif
497 		req->the_slot ();
498 
499 	} else if (req->type == TouchDisplay) {
500 
501 		req->display->touch ();
502 		if (req->display->delete_after_touch()) {
503 			delete req->display;
504 		}
505 
506 	} else if (req->type == StateChange) {
507 
508 		req->widget->set_state (req->new_state);
509 
510 	} else if (req->type == SetTip) {
511 
512 		gchar* old = gtk_widget_get_tooltip_markup (req->widget->gobj());
513 		if (
514 				(old && req->msg && strcmp (old, req->msg))
515 				||
516 				((old == NULL) != (req->msg == NULL || req->msg[0] == '\0'))
517 			 ) {
518 			gtk_widget_set_tooltip_markup (req->widget->gobj(), req->msg);
519 		}
520 		g_free (old);
521 
522 	} else {
523 
524 		error << "GtkUI: unknown request type "
525 		      << (int) req->type
526 		      << endmsg;
527 	}
528 }
529 
530 /*======================================================================
531   Error Display
532   ======================================================================*/
533 
534 void
dump_errors(std::ostream & ostr,size_t limit)535 UI::dump_errors (std::ostream& ostr, size_t limit)
536 {
537 	Glib::Threads::Mutex::Lock lm (error_lock);
538 	bool first = true;
539 
540 	if (limit > 0) {
541 		/* reverse listing, Errors only */
542 		for (list<string>::reverse_iterator i = error_stack.rbegin(); i != error_stack.rend(); ++i) {
543 			if ((*i).substr (0, 9) == X_("WARNING: ") || (*i).substr (0, 6) == X_("INFO: ")) {
544 				continue;
545 			}
546 			if (first) {
547 				first = false;
548 			}
549 			ostr << *i << endl;
550 			if (--limit == 0) {
551 				ostr << "..." << endl;
552 				break;
553 			}
554 		}
555 	}
556 
557 	if (first) {
558 		for (list<string>::const_iterator i = error_stack.begin(); i != error_stack.end(); ++i) {
559 			if (first) {
560 				ostr << endl << X_("Log Messages:") << endl;
561 				first = false;
562 			}
563 			ostr << *i << endl;
564 			if (limit > 0) {
565 				if (--limit == 0) {
566 					ostr << "..." << endl;
567 					break;
568 				}
569 			}
570 		}
571 	}
572 	ostr << endl;
573 }
574 
575 void
receive(Transmitter::Channel chn,const char * str)576 UI::receive (Transmitter::Channel chn, const char *str)
577 {
578 	{
579 		Glib::Threads::Mutex::Lock lm (error_lock);
580 		switch (chn) {
581 		case Transmitter::Fatal:
582 			error_stack.push_back (string (X_("FATAL: ")) + str);
583 			break;
584 		case Transmitter::Error:
585 			error_stack.push_back (string (X_("ERROR: ")) + str);
586 			break;
587 		case Transmitter::Warning:
588 			error_stack.push_back (string (X_("WARNING: ")) + str);
589 			break;
590 		case Transmitter::Info:
591 			error_stack.push_back (string (X_("INFO: ")) + str);
592 			break;
593 		case Transmitter::Debug:
594 			error_stack.push_back (string (X_("Debug: ")) + str);
595 			break;
596 		case Transmitter::Throw:
597 			error_stack.push_back (string (X_("THROW: ")) + str);
598 			break;
599 		}
600 	}
601 
602 	if (caller_is_ui_thread()) {
603 		process_error_message (chn, str);
604 	} else {
605 		UIRequest* req = get_request (ErrorMessage);
606 
607 		if (req == 0) {
608 			return;
609 		}
610 
611 		req->chn = chn;
612 		req->msg = strdup (str);
613 
614 		send_request (req);
615 	}
616 }
617 
618 void
process_error_message(Transmitter::Channel chn,const char * str)619 UI::process_error_message (Transmitter::Channel chn, const char *str)
620 {
621 	RefPtr<Style> style;
622 	RefPtr<TextBuffer::Tag> ptag;
623 	RefPtr<TextBuffer::Tag> mtag;
624 	const char *prefix;
625 	size_t prefix_len;
626 	bool fatal_received = false;
627 
628 	switch (chn) {
629 	case Transmitter::Fatal:
630 		prefix = "[FATAL]: ";
631 		ptag = fatal_ptag;
632 		mtag = fatal_mtag;
633 		prefix_len = 9;
634 		fatal_received = true;
635 		break;
636 	case Transmitter::Error:
637 		prefix = "[ERROR]: ";
638 		ptag = error_ptag;
639 		mtag = error_mtag;
640 		prefix_len = 9;
641 		break;
642 	case Transmitter::Warning:
643 		prefix = "[WARNING]: ";
644 		ptag = warning_ptag;
645 		mtag = warning_mtag;
646 		prefix_len = 11;
647 		break;
648 	case Transmitter::Info:
649 		prefix = "[INFO]: ";
650 		ptag = info_ptag;
651 		mtag = info_mtag;
652 		prefix_len = 8;
653 		break;
654 	case Transmitter::Debug:
655 		prefix = "[DEBUG]: ";
656 		ptag = debug_ptag;
657 		mtag = debug_mtag;
658 		prefix_len = 9;
659 		break;
660 	default:
661 		/* no choice but to use text/console output here */
662 		cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
663 		::exit (EXIT_FAILURE);
664 	}
665 
666 	errors->text().get_buffer()->begin_user_action();
667 
668 	if (fatal_received) {
669 		handle_fatal (str);
670 	} else {
671 
672 		if (!ptag || !mtag) {
673 			/* oops, message sent before we set up tags - don't crash */
674 			cerr << prefix << str << endl;
675 		} else {
676 			display_message (prefix, prefix_len, ptag, mtag, str);
677 		}
678 	}
679 
680 	errors->text().get_buffer()->end_user_action();
681 }
682 
683 void
show_errors()684 UI::show_errors ()
685 {
686 	Glib::RefPtr<ToggleAction> tact = ActionManager::get_toggle_action (X_("Editor"), X_("toggle-log-window"));
687 	tact->set_active ();
688 }
689 
690 void
toggle_errors()691 UI::toggle_errors ()
692 {
693 	Glib::RefPtr<ToggleAction> tact = ActionManager::get_toggle_action (X_("Editor"), X_("toggle-log-window"));
694 	if (tact->get_active()) {
695 		errors->set_position (WIN_POS_MOUSE);
696 		errors->show ();
697 	} else {
698 		errors->hide ();
699 	}
700 }
701 
702 void
display_message(const char * prefix,gint,RefPtr<TextBuffer::Tag> ptag,RefPtr<TextBuffer::Tag> mtag,const char * msg)703 UI::display_message (const char *prefix, gint /*prefix_len*/, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
704 {
705 	RefPtr<TextBuffer> buffer (errors->text().get_buffer());
706 	Glib::DateTime tm (g_date_time_new_now_local ());
707 
708 	buffer->insert_with_tag(buffer->end(), tm.format ("%FT%H:%M:%S "), ptag);
709 	buffer->insert_with_tag(buffer->end(), prefix, ptag);
710 	buffer->insert_with_tag(buffer->end(), msg, mtag);
711 	buffer->insert_with_tag(buffer->end(), "\n", mtag);
712 
713 	errors->scroll_to_bottom ();
714 }
715 
716 void
handle_fatal(const char * message)717 UI::handle_fatal (const char *message)
718 {
719 	Dialog win;
720 	Label label (message);
721 	Button quit (_("Press To Exit"));
722 	HBox hpacker;
723 
724 	win.set_default_size (400, 100);
725 
726 	WindowTitle title(Glib::get_application_name());
727 	title += ": Fatal Error";
728 	win.set_title (title.get_string());
729 
730 	win.set_position (WIN_POS_MOUSE);
731 	win.set_border_width (12);
732 
733 	win.get_vbox()->pack_start (label, true, true);
734 	hpacker.pack_start (quit, true, false);
735 	win.get_vbox()->pack_start (hpacker, false, false);
736 
737 	quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
738 
739 	win.show_all ();
740 	win.set_modal (true);
741 
742 	theMain->run ();
743 
744 	_exit (1);
745 }
746 
747 void
popup_error(const string & text)748 UI::popup_error (const string& text)
749 {
750 	if (!caller_is_ui_thread()) {
751 		error << "non-UI threads can't use UI::popup_error"
752 		      << endmsg;
753 		return;
754 	}
755 
756 	MessageDialog msg (text);
757 	msg.set_title (string_compose (_("I'm sorry %1, I can't do that"), g_get_user_name()));
758 	msg.set_wmclass (X_("error"), Glib::get_application_name());
759 	msg.set_position (WIN_POS_MOUSE);
760 	msg.run ();
761 }
762 
763 void
flush_pending(float timeout)764 UI::flush_pending (float timeout)
765 {
766 	if (!caller_is_ui_thread()) {
767 		error << "non-UI threads cannot call UI::flush_pending()"
768 		      << endmsg;
769 		return;
770 	}
771 
772 	int64_t end = g_get_monotonic_time () + timeout * 1e6;
773 
774 	gtk_main_iteration();
775 
776 	while (gtk_events_pending()) {
777 		if (timeout > 0 && end < g_get_monotonic_time ()) {
778 			cerr << "UI::flush_pending timed out after " << timeout << "s.\n";
779 			break;
780 		}
781 		gtk_main_iteration();
782 	}
783 }
784 
785 bool
just_hide_it(GdkEventAny *,Window * win)786 UI::just_hide_it (GdkEventAny* /*ev*/, Window *win)
787 {
788 	win->hide ();
789 	return true;
790 }
791 
792 void
color_selection_done(bool status)793 UI::color_selection_done (bool status)
794 {
795 	color_picked = status;
796 	Main::quit ();
797 }
798 
799 bool
color_selection_deleted(GdkEventAny *)800 UI::color_selection_deleted (GdkEventAny* /*ev*/)
801 {
802 	Main::quit ();
803 	return true;
804 }
805