1 /* gobby - A GTKmm driven libobby client
2  * Copyright (C) 2005-2006 0x539 dev group
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 #include <stdexcept>
20 #include <fstream>
21 #include <ostream>
22 
23 #include <glibmm/miscutils.h>
24 #include <gtkmm/main.h>
25 #include <gtkmm/aboutdialog.h>
26 #include <gtkmm/messagedialog.h>
27 #include <gtkmm/filechooserdialog.h>
28 #include <gtkmm/stock.h>
29 
30 #include <obby/format_string.hpp>
31 #include <obby/client_buffer.hpp>
32 #include <obby/host_buffer.hpp>
33 
34 #include "common.hpp"
35 #include "encoding.hpp"
36 #include "encoding_selector.hpp"
37 #include "docwindow.hpp"
38 #include "passworddialog.hpp"
39 #include "entrydialog.hpp"
40 #include "preferencesdialog.hpp"
41 #include "joinprogressdialog.hpp"
42 #include "hostprogressdialog.hpp"
43 #include "window.hpp"
44 #include "features.hpp"
45 #include "icon.hpp"
46 #include "colorsel.hpp"
47 
Window(const IconManager & icon_mgr,Config & config)48 Gobby::Window::Window(const IconManager& icon_mgr, Config& config):
49 	Gtk::Window(Gtk::WINDOW_TOPLEVEL), m_config(config),
50 #ifdef WITH_GTKSOURCEVIEW2
51 	m_lang_manager(gtk_source_language_manager_new()),
52 #else
53 	m_lang_manager(gtk_source_languages_manager_new()),
54 #endif
55 	m_preferences(m_config, m_lang_manager), m_icon_mgr(icon_mgr),
56 	m_application_state(APPLICATION_NONE),
57 	m_document_settings(*this),
58 	m_header(m_application_state, m_lang_manager),
59 	m_folder(m_header, m_preferences),
60 	m_userlist(
61 		*this,
62 		m_header,
63 		m_folder,
64 		m_preferences,
65 		config.get_root()["windows"]
66 	),
67 	m_documentlist(
68 		*this,
69 		m_document_settings,
70 		m_header,
71 		m_folder,
72 		m_preferences,
73 		config.get_root()["windows"]
74 	),
75 	m_chat(*this, m_preferences),
76 	m_statusbar(m_header, m_folder)
77 #ifdef WITH_AVAHI
78 	,m_glib_poll(avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT))
79 #endif
80 {
81 	// Header
82 	m_header.action_app_session_create->signal_activate().connect(
83 		sigc::mem_fun(*this, &Window::on_session_create) );
84 	m_header.action_app_session_join->signal_activate().connect(
85 		sigc::mem_fun(*this, &Window::on_session_join) );
86 	m_header.action_app_session_save->signal_activate().connect(
87 		sigc::mem_fun(*this, &Window::on_session_save) );
88 	m_header.action_app_session_save_as->signal_activate().connect(
89 		sigc::mem_fun(*this, &Window::on_session_save_as) );
90 	m_header.action_app_session_quit->signal_activate().connect(
91 		sigc::mem_fun(*this, &Window::on_session_quit) );
92 	m_header.action_app_quit->signal_activate().connect(
93 		sigc::mem_fun(*this, &Window::on_quit) );
94 
95 	m_header.action_session_document_create->signal_activate().connect(
96 		sigc::mem_fun(*this, &Window::on_document_create) );
97 	m_header.action_session_document_open->signal_activate().connect(
98 		sigc::mem_fun(*this, &Window::on_document_open) );
99 	m_header.action_session_document_save->signal_activate().connect(
100 		sigc::mem_fun(*this, &Window::on_document_save) );
101 	m_header.action_session_document_save_as->signal_activate().connect(
102 		sigc::mem_fun(*this, &Window::on_document_save_as) );
103 	m_header.action_session_document_save_all->signal_activate().connect(
104 		sigc::mem_fun(*this, &Window::on_document_save_all) );
105 	m_header.action_session_document_close->signal_activate().connect(
106 		sigc::mem_fun(*this, &Window::on_document_close) );
107 
108 	m_header.action_edit_search->signal_activate().connect(
109 		sigc::mem_fun(*this, &Window::on_edit_search) );
110 	m_header.action_edit_search_replace->signal_activate().connect(
111 		sigc::mem_fun(*this, &Window::on_edit_search_replace) );
112 	m_header.action_edit_goto_line->signal_activate().connect(
113 		sigc::mem_fun(*this, &Window::on_edit_goto_line) );
114 	m_header.action_edit_preferences->signal_activate().connect(
115 		sigc::mem_fun(*this, &Window::on_edit_preferences) );
116 
117 	m_header.action_user_set_colour->signal_activate().connect(
118 		sigc::mem_fun(*this, &Window::on_user_set_colour) );
119 	m_header.action_user_set_password->signal_activate().connect(
120 		sigc::mem_fun(*this, &Window::on_user_set_password) );
121 
122 	m_header.action_edit_document_preferences->signal_activate().connect(
123 		sigc::mem_fun(*this, &Window::on_view_preferences) );
124 
125 	m_header.action_window_chat->signal_activate().connect(
126 		sigc::mem_fun(*this, &Window::on_window_chat) );
127 
128 	m_header.action_help_about->signal_activate().connect(
129 		sigc::mem_fun(*this, &Window::on_about) );
130 
131 	// Folder
132 	m_folder.document_add_event().connect(
133 		sigc::mem_fun(*this, &Window::on_folder_document_add) );
134 	m_folder.document_remove_event().connect(
135 		sigc::mem_fun(*this, &Window::on_folder_document_remove) );
136 	m_folder.document_close_request_event().connect(
137 		sigc::mem_fun(*this, &Window::on_folder_document_close_request) );
138 	m_folder.tab_switched_event().connect(
139 		sigc::mem_fun(*this, &Window::on_folder_tab_switched) );
140 
141 	// Settings
142 	m_document_settings.document_insert_event().connect(
143 		sigc::mem_fun(*this, &Window::on_settings_document_insert) );
144 
145 	m_conn_chat_realize = m_chat.signal_realize().connect(
146 		sigc::mem_fun(*this, &Window::on_chat_realize) );
147 
148 	// Build UI
149 	add_accel_group(m_header.get_accel_group() );
150 
151 	m_frame_chat.set_shadow_type(Gtk::SHADOW_IN);
152 	m_frame_text.set_shadow_type(Gtk::SHADOW_IN);
153 
154 	m_frame_chat.add(m_chat);
155 	m_frame_text.add(m_folder);
156 
157 	m_mainpaned.pack1(m_frame_text, true, false);
158 	m_mainpaned.pack2(m_frame_chat, true, false);
159 
160 	m_mainbox.pack_start(m_header, Gtk::PACK_SHRINK);
161 	m_mainbox.pack_start(m_mainpaned, Gtk::PACK_EXPAND_WIDGET);
162 	m_mainbox.pack_start(m_statusbar, Gtk::PACK_SHRINK);
163 
164 	add(m_mainbox);
165 
166 	// Apply initial preferences
167 	apply_preferences();
168 
169 	Config::ParentEntry& windows = config.get_root()["windows"];
170 	bool show_chat = windows["chat"].get_value<bool>(
171 		"visible",
172 		true
173 	);
174 
175 	m_header.action_window_chat->set_active(show_chat);
176 	m_application_state.modify(APPLICATION_INITIAL, APPLICATION_NONE);
177 
178 	show_all_children();
179 	if(!show_chat) m_frame_chat.hide_all();
180 
181 	set_title("Gobby");
182 	set_default_size(640, 480);
183 
184 #ifdef WITH_ZEROCONF
185 	// Initialise Zeroconf
186 	try
187 	{
188 #ifdef WITH_AVAHI
189 		m_zeroconf.reset(new obby::zeroconf_avahi(avahi_glib_poll_get(m_glib_poll)));
190 #else
191 		m_zeroconf.reset(new obby::zeroconf);
192 		// Periodically check for events when not using Avahi Glib Poll
193 		Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(*m_zeroconf.get(), &zeroconf_base::select), 0), 1500);
194 #endif
195 	}
196 	catch(std::runtime_error&)
197 	{
198 		std::cerr << _("Zeroconf initialisation failed. Probably you "
199 			"need to run avahi-daemon or mDNSResponder, depending "
200 			"on the library you use, as root prior to Gobby. "
201 			"Zeroconf support is deactivated for this session.");
202 		std::cerr << std::endl;
203 		m_zeroconf.reset();
204 	}
205 #endif
206 
207 	if(m_preferences.appearance.remember)
208 	{
209 		Config::ParentEntry& screen = config.get_root()["screen"];
210 
211 		// Restore the window's position from the configuration
212 		const int x = windows["main"].get_value<int>("x", 0);
213 		const int y = windows["main"].get_value<int>("y", 0);
214 		const int w = windows["main"].get_value<int>("width", 0);
215 		const int h = windows["main"].get_value<int>("height", 0);
216 
217 		const int s_w = screen.get_value<int>("width", 0);
218 		const int s_h = screen.get_value<int>("height", 0);
219 		bool first_run = (x == 0 && y == 0 && w == 0 && h == 0);
220 
221 		Glib::RefPtr<Gdk::Screen> scr(get_screen() );
222 		if( (scr->get_width() >= s_w && scr->get_height() >= s_h) &&
223 		    (!first_run) )
224 		{
225 			move(x, y);
226 			resize(w, h);
227 		}
228 	}
229 }
230 
~Window()231 Gobby::Window::~Window()
232 {
233 	if(m_buffer.get() && m_buffer->is_open() )
234 		obby_end();
235 
236 	// Serialise preferences into config
237 	m_preferences.serialise(m_config);
238 
239 	Config::ParentEntry& windows = m_config.get_root()["windows"];
240 	windows["chat"].set_value(
241 		"visible",
242 		m_header.action_window_chat->get_active()
243 	);
244 
245 	// Save the window's current position
246 	if(m_preferences.appearance.remember)
247 	{
248 		int x, y, w, h;
249 		get_position(x, y); get_size(w, h);
250 		Glib::RefPtr<Gdk::Screen> scr(get_screen() );
251 
252 		windows["main"].set_value("x", x);
253 		windows["main"].set_value("y", y);
254 		windows["main"].set_value("width", w);
255 		windows["main"].set_value("height", h);
256 
257 		Config::ParentEntry& screen = m_config.get_root()["screen"];
258 		screen.set_value("width", scr->get_width() );
259 		screen.set_value("height", scr->get_height() );
260 	}
261 
262 	/* Free explictely to make sure that the avahi poll is no longer
263 	 * referenced when we free it */
264 #ifdef WITH_ZEROCONF
265 	m_zeroconf.reset(NULL);
266 #endif
267 
268 #ifdef WITH_AVAHI
269 	avahi_glib_poll_free(m_glib_poll);
270 #endif
271 }
272 
on_delete_event(GdkEventAny * event)273 bool Gobby::Window::on_delete_event(GdkEventAny* event)
274 {
275 	if(m_buffer.get() == NULL) return false;
276 	if(!m_buffer->is_open() ) return false;
277 
278 	Gtk::MessageDialog dlg(
279 		*this,
280 		_("You are still connected to a session"),
281 		false,
282 		Gtk::MESSAGE_WARNING,
283 		Gtk::BUTTONS_NONE,
284 		true
285 	);
286 
287 	dlg.set_secondary_text(
288 		_("Do you want to close Gobby anyway?")
289 	);
290 
291 	Gtk::Image* img = Gtk::manage(new Gtk::Image(Gtk::Stock::CANCEL,
292 	                                             Gtk::ICON_SIZE_BUTTON));
293 	Gtk::Button* cancel_button
294 		= dlg.add_button(_("C_ancel"), Gtk::RESPONSE_CANCEL);
295 	dlg.add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_YES);
296 	cancel_button->set_image(*img);
297 	cancel_button->grab_focus();
298 
299 	return dlg.run() != Gtk::RESPONSE_YES;
300 }
301 
on_realize()302 void Gobby::Window::on_realize()
303 {
304 	Gtk::Window::on_realize();
305 
306 	// Create new IPC instance
307 	try
308 	{
309 		m_ipc.reset(new Ipc::LocalInstance);
310 		m_ipc->file_event().connect(
311 			sigc::mem_fun(*this, &Window::on_ipc_file)
312 		);
313 	}
314 	catch(net6::error& e)
315 	{
316 		// Whatever...
317 		display_error(e.what() );
318 	}
319 }
320 
on_chat_realize()321 void Gobby::Window::on_chat_realize()
322 {
323 	m_mainpaned.set_position(m_mainpaned.get_height() * 3 / 5);
324 	m_conn_chat_realize.disconnect();
325 }
326 
obby_start()327 void Gobby::Window::obby_start()
328 {
329 	// Connect to obby events
330 	m_buffer->user_join_event().connect(
331 		sigc::mem_fun(*this, &Window::on_obby_user_join) );
332 	m_buffer->user_part_event().connect(
333 		sigc::mem_fun(*this, &Window::on_obby_user_part) );
334 	m_buffer->user_colour_event().connect(
335 		sigc::mem_fun(*this, &Window::on_obby_user_colour) );
336 	m_buffer->user_colour_failed_event().connect(
337 		sigc::mem_fun(*this, &Window::on_obby_user_colour_failed) );
338 
339 	m_buffer->document_insert_event().connect(
340 		sigc::mem_fun(*this, &Window::on_obby_document_insert) );
341 	m_buffer->document_remove_event().connect(
342 		sigc::mem_fun(*this, &Window::on_obby_document_remove) );
343 
344 	// Accept drag and drop of files into the gobby window
345 	m_dnd.reset(new DragDrop(*this) );
346 
347 	// Delegate start of obby session
348 	m_folder.obby_start(*m_buffer);
349 	m_documentlist.obby_start(*m_buffer);
350 	m_document_settings.obby_start(*m_buffer);
351 	m_userlist.obby_start(*m_buffer);
352 	m_chat.obby_start(*m_buffer);
353 	m_statusbar.obby_start(*m_buffer);
354 
355 	// Forward user joins
356 	const obby::user_table& table = m_buffer->get_user_table();
357 	for(obby::user_table::iterator iter =
358 		table.begin(obby::user::flags::NONE, obby::user::flags::NONE);
359 	    iter != table.end(obby::user::flags::NONE, obby::user::flags::NONE);
360 	    ++ iter)
361 	{
362 		on_obby_user_join(*iter);
363 	}
364 
365 	// Send documents to components
366 	Buffer::document_iterator iter = m_buffer->document_begin();
367 	for(; iter != m_buffer->document_end(); ++ iter)
368 		on_obby_document_insert(*iter);
369 
370 	// Set last page as active one because it is currently shown anyway.
371 	//if(m_buffer->document_count() > 0)
372 	//	m_folder.set_current_page(m_buffer->document_count() - 1);
373 
374 	// Clear location of previous session file, this is a new session
375 	m_prev_session = "";
376 
377 	// Current document has changed, update titlebar
378 	update_title_bar();
379 
380 	// Show up document list if obby buffer contains documents
381 	if(m_buffer->document_count() > 0)
382 	{
383 		m_documentlist.show();
384 		m_documentlist.grab_focus();
385 	}
386 
387 	ApplicationFlags inc_flags = APPLICATION_SESSION;
388 	ApplicationFlags exc_flags = APPLICATION_INITIAL | APPLICATION_DOCUMENT;
389 
390 	if(dynamic_cast<ClientBuffer*>(m_buffer.get()) != NULL)
391 		exc_flags |= APPLICATION_HOST;
392 	else
393 		inc_flags |= APPLICATION_HOST;
394 
395 	m_application_state.modify(inc_flags, exc_flags);
396 }
397 
obby_end()398 void Gobby::Window::obby_end()
399 {
400 	// Nothing to do if no buffer is open
401 	if(m_buffer.get() == NULL)
402 	{
403 		throw std::logic_error(
404 			"Gobby::Window::obby_end:\n"
405 			"Buffer not available"
406 		);
407 	}
408 
409 	m_application_state.modify(APPLICATION_NONE, APPLICATION_SESSION);
410 
411 	if(m_buffer->is_open() )
412 	{
413 		// TODO: Virtual close call in obby?
414 		ClientBuffer* client_buf =
415 			dynamic_cast<ClientBuffer*>(m_buffer.get());
416 		HostBuffer* host_buf =
417 			dynamic_cast<HostBuffer*>(m_buffer.get());
418 
419 		if(client_buf != NULL) client_buf->disconnect();
420 		if(host_buf != NULL) host_buf->close();
421 	}
422 
423 	// Remove DND handler
424 	m_dnd.reset(NULL);
425 
426 	// Tell GUI components that the session ended
427 	m_folder.obby_end();
428 	m_document_settings.obby_end();
429 	m_userlist.obby_end();
430 	m_documentlist.obby_end();
431 	m_chat.obby_end();
432 	m_statusbar.obby_end();
433 
434 #ifdef WITH_ZEROCONF
435 	if(m_zeroconf.get() )
436 		m_zeroconf->unpublish_all();
437 #endif
438 }
439 
on_session_create()440 void Gobby::Window::on_session_create()
441 {
442 	session_open(true);
443 }
444 
on_session_join()445 void Gobby::Window::on_session_join()
446 {
447 	session_join(true);
448 }
449 
on_session_save()450 void Gobby::Window::on_session_save()
451 {
452 	// Call the dialog if we have no previos filename
453 	if(m_prev_session.empty()) {
454 		on_session_save_as();
455 	} else {
456 		// Just overwrite if we already were writing there
457 		try
458 		{
459 			m_buffer->serialise(m_prev_session);
460 		}
461 		catch(std::exception& e)
462 		{
463 			display_error(e.what() );
464 		}
465 	}
466 }
467 
on_session_save_as()468 void Gobby::Window::on_session_save_as()
469 {
470 	Gtk::CheckButton m_chk_default_ext(
471 		_("Use default .obby extension if none is given")
472 	);
473 
474 	Gtk::FileChooserDialog dlg(
475 		*this,
476 		_("Save obby session"),
477 		Gtk::FILE_CHOOSER_ACTION_SAVE
478 	);
479 
480 	m_chk_default_ext.set_active(true);
481 	dlg.get_vbox()->pack_start(m_chk_default_ext, Gtk::PACK_SHRINK);
482 	// This option confuses the overwrite confirmation :/
483 	//m_chk_default_ext.show();
484 
485 #ifdef GTKMM_GEQ_28
486 	dlg.set_do_overwrite_confirmation(true);
487 #endif
488 
489 	// Use the location of a previously saved session, if any
490 	if(!m_prev_session.empty() )
491 	{
492 		dlg.set_filename(m_prev_session);
493 	}
494 	else
495 	{
496 		// Use the last used path for this dialog, if we have any
497 		if(!m_last_path.empty() )
498 			dlg.set_current_folder(m_last_path);
499 	}
500 
501 	dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
502 	dlg.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
503 
504 	if(dlg.run() == Gtk::RESPONSE_OK)
505 	{
506 		// Use current folder as standard folder for other dialogs
507 		m_last_path = dlg.get_current_folder();
508 		// Get selected filename
509 		std::string filename = dlg.get_filename();
510 
511 		// Append .obby extension if none is given
512 		/*if(m_chk_default_ext.get_active() )
513 			if(filename.find('.') == std::string::npos)
514 				filename += ".obby";*/
515 
516 		// Save document
517 		try
518 		{
519 			m_buffer->serialise(filename);
520 			m_prev_session = filename;
521 		}
522 		catch(std::exception& e)
523 		{
524 			display_error(e.what() );
525 		}
526 	}
527 }
528 
on_session_quit()529 void Gobby::Window::on_session_quit()
530 {
531 	obby_end();
532 }
533 
on_about()534 void Gobby::Window::on_about()
535 {
536 	Gtk::AboutDialog dlg;
537 	dlg.set_name("Gobby");
538 	dlg.set_version(PACKAGE_VERSION);
539 	dlg.set_comments(_("A collaborative text editor"));
540 
541 	dlg.set_copyright(
542 		"Copyright (C) 2005-2008 0x539 dev group <crew@0x539.de>"
543 	);
544 
545 	dlg.set_logo_icon_name("gobby");
546 
547 	std::deque<Glib::ustring> authors;
548 	authors.push_back("Developers:");
549 	authors.push_back("  Armin Burgmeier <armin@0x539.de>");
550 	authors.push_back("  Philipp Kern <phil@0x539.de>");
551 	authors.push_back("");
552 	authors.push_back("Contributors:");
553 	authors.push_back("  Benjamin Herr <ben@0x539.de>");
554 
555 	std::deque<Glib::ustring> artists;
556 	artists.push_back("Logo:");
557 	artists.push_back("  Thomas Glatt <tom@0x539.de>");
558 	artists.push_back("");
559 	artists.push_back("Additional artwork:");
560 	artists.push_back("  Thomas Glatt <tom@0x539.de>");
561 	artists.push_back("  Benjamin Herr <ben@0x539.de>");
562 
563 	dlg.set_authors(authors);
564 	dlg.set_artists(artists);
565 
566 	dlg.set_license(
567 		// Please note in the translation that only the English version is
568 		// legally binding.
569 		_("This program is free software; you can redistribute it\n"
570 		"and/or modify it under the terms of the GNU General Public\n"
571 		"License as published by the Free Software Foundation; either\n"
572 		"version 2 of the License, or (at your option) any later\n"
573 		"version.\n"
574 		"\n"
575 		"This program is distributed in the hope that it will be\n"
576 		"useful, but WITHOUT ANY WARRANTY; without even the implied\n"
577 		"warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR\n"
578 		"PURPOSE.  See the GNU General Public License for more details.")
579 	);
580 	dlg.run();
581 }
582 
on_folder_document_add(DocWindow & window)583 void Gobby::Window::on_folder_document_add(DocWindow& window)
584 {
585 	// Select newly created page if not automatically opened
586 	if(!m_document_settings.get_automatically_opened(window.get_info() ))
587 	{
588 		m_folder.set_current_page(m_folder.page_num(window) );
589 		window.grab_focus();
590 	}
591 
592 	// Unset modifified flag when locally opened
593 	if(!m_local_file_path.empty())
594 	{
595 		gtk_text_buffer_set_modified(
596 			GTK_TEXT_BUFFER(window.get_document().get_buffer()), FALSE);
597 	}
598 
599 	if(m_folder.get_n_pages() == 1)
600 	{
601 		// There have not been any documents before
602 		m_application_state.modify(
603 			APPLICATION_DOCUMENT,
604 			APPLICATION_NONE
605 		);
606 
607 		m_folder.set_show_tabs(false);
608 	}
609 	else
610 	{
611 		m_folder.set_show_tabs(true);
612 	}
613 }
614 
on_folder_document_remove(DocWindow & window)615 void Gobby::Window::on_folder_document_remove(DocWindow& window)
616 {
617 	// Update title bar if there are no more documents left
618 	// (folder_tab_switched is not emitted in this case)
619 	if(m_folder.get_n_pages() == 0)
620 	{
621 		update_title_bar();
622 
623 		m_application_state.modify(
624 			APPLICATION_NONE,
625 			APPLICATION_DOCUMENT
626 		);
627 	}
628 	else if(m_folder.get_n_pages() == 1)
629 	{
630 		m_folder.set_show_tabs(false);
631 	}
632 }
633 
on_folder_document_close_request(DocWindow & window)634 void Gobby::Window::on_folder_document_close_request(DocWindow& window)
635 {
636 	close_document(window);
637 }
638 
on_folder_tab_switched(DocWindow & window)639 void Gobby::Window::on_folder_tab_switched(DocWindow& window)
640 {
641 	// Update title bar
642 	update_title_bar();
643 }
644 
on_settings_document_insert(LocalDocumentInfo & info)645 void Gobby::Window::on_settings_document_insert(LocalDocumentInfo& info)
646 {
647 	// Mark automatically opened documents and subscribe to them.
648 	if(m_preferences.behaviour.auto_open_new_documents
649 		&& !info.is_subscribed() )
650 	{
651 		m_document_settings.set_automatically_opened(info, true);
652 		info.subscribe();
653 	}
654 
655 	// Set the path from which this document was opened,
656 	// if we opened that file.
657 	if(info.get_owner() == &m_buffer->get_self() &&
658 	   !m_local_file_path.empty() )
659 	{
660 		// " " is newly created, so we do not need a path
661 		if(m_local_file_path != " ")
662 		{
663 			m_document_settings.set_path(info, m_local_file_path);
664 		}
665 
666 		m_document_settings.set_original_encoding(
667 			info,
668 			m_local_encoding
669 		);
670 	}
671 	else
672 	{
673 		// File was opened remotely, so we do not know anything
674 		// about the original encoding, so assume it's UTF-8.
675 		m_document_settings.set_original_encoding(info, "UTF-8");
676 	}
677 }
678 
on_document_create()679 void Gobby::Window::on_document_create()
680 {
681 	EntryDialog dlg(*this, _("Create document"), _("Enter document name"));
682 	dlg.set_check_valid_entry(true);
683 
684 	if(dlg.run() == Gtk::RESPONSE_OK)
685 	{
686 		// " " means a newly created file
687 		m_local_file_path = " ";
688 		m_local_encoding = "UTF-8";
689 		// Create new document
690 		m_buffer->document_create(dlg.get_text(), "UTF-8", "");
691 
692 		// Clear local path
693 		m_local_file_path.clear();
694 		m_local_encoding.clear();
695 	}
696 }
697 
on_document_open()698 void Gobby::Window::on_document_open()
699 {
700 	// Create FileChooser
701 	EncodingFileChooserDialog dlg(
702 		*this,
703 		_("Open new document"),
704 		Gtk::FILE_CHOOSER_ACTION_OPEN
705 	);
706 
707 	dlg.get_selector().set_encoding(EncodingSelector::AUTO_DETECT);
708 
709 	// Use the last used path for this dialog, if we have any
710 	if(!m_last_path.empty() )
711 		dlg.set_current_folder(m_last_path);
712 
713 	// Create buttons to close it
714 	dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
715 	dlg.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
716 
717 	// Allow multi selection
718 	dlg.set_select_multiple(true);
719 
720 	// Show FileChooser
721 	if(dlg.run() == Gtk::RESPONSE_OK)
722 	{
723 		// Use current folder as standard folder for later dialogs
724 		m_last_path = dlg.get_current_folder();
725 		// Open chosen files
726 		std::list<Glib::ustring> list = dlg.get_filenames();
727                 std::string encoding = dlg.get_selector().get_encoding();
728 		for(std::list<Glib::ustring>::iterator iter = list.begin();
729 		    iter != list.end();
730 		    ++ iter)
731 		{
732 			open_local_file(*iter, encoding);
733 		}
734 	}
735 }
736 
on_document_save()737 void Gobby::Window::on_document_save()
738 {
739 	handle_document_save();
740 }
741 
handle_document_save_impl(DocWindow * doc)742 bool Gobby::Window::handle_document_save_impl(DocWindow* doc)
743 {
744 	// Is there already a path for this document?
745 	std::string path = m_document_settings.get_path(doc->get_info() );
746 	if(!path.empty() )
747 	{
748 		// Yes, so save the document there
749 		save_local_file(
750 			*doc,
751 			path,
752 			m_document_settings.get_original_encoding(
753 				doc->get_info()
754 			)
755 		);
756 		return true;
757 	}
758 	else
759 	{
760 		// Open save as dialog otherwise
761 		return handle_document_save_as_impl(doc);
762 	}
763 }
764 
handle_document_save()765 bool Gobby::Window::handle_document_save()
766 {
767 	// Get page
768 	DocWindow* doc = get_current_document();
769 
770 	if(doc == NULL)
771 	{
772 		throw std::logic_error(
773 			"Gobby::Window::on_document_save:\n"
774 			"No document opened"
775 		);
776 	}
777 
778 	return handle_document_save_impl(doc);
779 }
780 
on_document_save_as()781 void Gobby::Window::on_document_save_as()
782 {
783 	handle_document_save_as();
784 }
785 
handle_document_save_as()786 bool Gobby::Window::handle_document_save_as()
787 {
788 	// Get page
789 	DocWindow* doc = get_current_document();
790 	if(doc == NULL)
791 	{
792 		throw std::logic_error(
793 			"Gobby::Window::on_document_save_as:\n"
794 			"No document opened"
795 		);
796 	}
797 
798 	return handle_document_save_as_impl(doc);
799 }
800 
handle_document_save_as_impl(DocWindow * doc)801 bool Gobby::Window::handle_document_save_as_impl(DocWindow* doc)
802 {
803 	// Window title
804 	obby::format_string str(_("Save document \"%0%\"") );
805 	str << doc->get_info().get_title();
806 
807 	// Setup dialog
808 	EncodingFileChooserDialog dlg(
809 		*this,
810 		str.str(),
811 		Gtk::FILE_CHOOSER_ACTION_SAVE
812 	);
813 
814 	// TODO: Preselect document's encoding
815 	dlg.get_selector().set_encoding(
816 		m_document_settings.get_original_encoding(doc->get_info())
817 	);
818 
819 #ifdef GTKMM_GEQ_28
820 	dlg.set_do_overwrite_confirmation(true);
821 #endif
822 
823 	std::string path = m_document_settings.get_path(doc->get_info() );
824 
825 	// Does the document have already a path?
826 	if(!path.empty() )
827 	{
828 		// Yes, so set it as filename
829 		dlg.set_filename(path);
830 	}
831 	else
832 	{
833 		// No, so use the last path a filesel dialog was closed with
834 		if(!m_last_path.empty() )
835 			dlg.set_current_folder(m_last_path);
836 
837 		// Set current title as proposed file name
838 		dlg.set_current_name(doc->get_info().get_title() );
839 	}
840 
841 	// Add buttons to close the dialog
842 	dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
843 	dlg.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
844 	dlg.set_default_response(Gtk::RESPONSE_OK);
845 
846 	if(dlg.run() == Gtk::RESPONSE_OK)
847 	{
848 		// Use current folder as standard folder for other dialogs
849 		m_last_path = dlg.get_current_folder();
850 		// Save document
851 		save_local_file(
852 			*doc,
853 			dlg.get_filename(),
854 			dlg.get_selector().get_encoding()
855 		);
856 		return true;
857 	}
858 	return false;
859 }
860 
on_document_save_all()861 void Gobby::Window::on_document_save_all()
862 {
863 	for(int i = 0; i < m_folder.get_n_pages(); ++i)
864 	{
865 		Widget* page = m_folder.get_nth_page(i);
866 		handle_document_save_impl(static_cast<DocWindow*>(page) );
867 	}
868 }
869 
on_document_close()870 void Gobby::Window::on_document_close()
871 {
872 	// Get current page
873 	Widget* page = m_folder.get_nth_page(m_folder.get_current_page() );
874 	// Close it
875 	close_document(*static_cast<DocWindow*>(page) );
876 }
877 
on_edit_search()878 void Gobby::Window::on_edit_search()
879 {
880 	if(m_finddialog.get() == NULL)
881 		m_finddialog.reset(new FindDialog(*this));
882 
883 	m_finddialog->set_search_only(true);
884 	m_finddialog->present();
885 }
886 
on_edit_search_replace()887 void Gobby::Window::on_edit_search_replace()
888 {
889 	if(m_finddialog.get() == NULL)
890 		m_finddialog.reset(new FindDialog(*this));
891 
892 	m_finddialog->set_search_only(false);
893 	m_finddialog->present();
894 }
895 
on_edit_goto_line()896 void Gobby::Window::on_edit_goto_line()
897 {
898 	if(m_gotodialog.get() == NULL)
899 		m_gotodialog.reset(new GotoDialog(*this));
900 
901 	m_gotodialog->present();
902 }
903 
on_edit_preferences()904 void Gobby::Window::on_edit_preferences()
905 {
906 	PreferencesDialog dlg(*this, m_preferences, m_lang_manager, false);
907 
908 	// Info label
909 	Gtk::Label lbl_info(_(
910 		"Click on \"Apply\" to apply the new settings to documents "
911 		"that are currently open. \"OK\" will just store the values "
912 		"to use them with newly created documents."
913 	) );
914 
915 	// Show info label and apply button if documents are open
916 	if(m_buffer.get() && m_buffer->document_count() > 0)
917 	{
918 		// TODO: How to get the label to use all available space?
919 		lbl_info.set_line_wrap(true);
920 		lbl_info.set_alignment(Gtk::ALIGN_LEFT);
921 
922 		dlg.get_vbox()->pack_start(lbl_info, Gtk::PACK_SHRINK);
923 		dlg.add_button(Gtk::Stock::APPLY, Gtk::RESPONSE_APPLY);
924 		lbl_info.show();
925 	}
926 
927 	int result = dlg.run();
928 	if(result == Gtk::RESPONSE_OK || result == Gtk::RESPONSE_APPLY)
929 	{
930 		// Use new preferences
931 		Preferences prefs;
932 		dlg.set(prefs);
933 		m_preferences = prefs;
934 
935 		// Apply window preferences
936 		apply_preferences();
937 
938 		// Apply preferences to open documents.
939 		if(result == Gtk::RESPONSE_APPLY)
940 		{
941 			for(int i = 0; i < m_folder.get_n_pages(); ++ i)
942 			{
943 				DocWindow& doc = *static_cast<DocWindow*>(
944 					m_folder.get_nth_page(i) );
945 				doc.set_preferences(m_preferences);
946 			}
947 		}
948 	}
949 }
950 
on_user_set_password()951 void Gobby::Window::on_user_set_password()
952 {
953 	// Build password dialog with info
954 	PasswordDialog dlg(*this, _("Set user password") );
955 	dlg.set_info(_(
956 		"Set a user password for your user account. When you try to "
957 		"login next time with this user, you will be prompted for your "
958 		"password."
959 	) );
960 
961 	// Run it
962 	if(dlg.run() == Gtk::RESPONSE_OK)
963 	{
964 		dynamic_cast<ClientBuffer*>(
965 			m_buffer.get() )->set_password(dlg.get_password() );
966 	}
967 }
968 
on_user_set_colour()969 void Gobby::Window::on_user_set_colour()
970 {
971 	// Simple ColorSelectionDialog
972 	ColorSelectionDialog dlg(m_config.get_root() );
973 	const obby::user& user = m_buffer->get_self();
974 	Gdk::Color color;
975 
976 	color.set_red(user.get_colour().get_red() * 65535 / 255);
977 	color.set_green(user.get_colour().get_green() * 65535 / 255);
978 	color.set_blue(user.get_colour().get_blue() * 65535 / 255);
979 	dlg.get_colorsel()->set_current_color(color);
980 
981 	// Run it
982 	if(dlg.run() == Gtk::RESPONSE_OK)
983 	{
984 		// Convert GDK color to obby color, set new color
985 		Gdk::Color color = dlg.get_colorsel()->get_current_color();
986 		m_buffer->set_colour(
987 			obby::colour(
988 				color.get_red() * 255 / 65535,
989 				color.get_green() * 255 / 65535,
990 				color.get_blue() * 255 / 65535
991 			)
992 		);
993 	}
994 }
995 
on_view_preferences()996 void Gobby::Window::on_view_preferences()
997 {
998 	// Get current page
999 	DocWindow* doc = get_current_document();
1000 	if(doc == NULL)
1001 	{
1002 		throw std::logic_error(
1003 			"Gobby::Window::on_view_preferences:\n"
1004 			"No window opened"
1005 		);
1006 	}
1007 
1008 	// Add preferences dialog
1009 	PreferencesDialog dlg(*this,
1010 		doc->get_preferences(),
1011 		m_lang_manager,
1012 		true
1013 	);
1014 
1015 	// Label text
1016 	obby::format_string str(_(
1017 		"These preferences affect only the currently active document "
1018 		"\"%0%\". If you want to change global preferences, use the "
1019 		"preferences menu item in the \"Edit\" menu."
1020 	) );
1021 
1022 	// Get title
1023 	str << doc->get_info().get_suffixed_title();
1024 
1025 	// Info label
1026 	Gtk::Label lbl_info(str.str() );
1027 
1028 	// TODO: How to get the label to use all available space?
1029 	lbl_info.set_line_wrap(true);
1030 	lbl_info.set_alignment(Gtk::ALIGN_LEFT);
1031 
1032 	// Add it into the dialog
1033 	dlg.get_vbox()->pack_start(lbl_info, Gtk::PACK_SHRINK);
1034 	dlg.get_vbox()->reorder_child(lbl_info, 0); // Push to top of dialog
1035 	lbl_info.show();
1036 
1037 	// Show the dialog
1038 	if(dlg.run() == Gtk::RESPONSE_OK)
1039 	{
1040 		// Apply new preferences to the document
1041 		Preferences prefs;
1042 		dlg.set(prefs);
1043 		doc->set_preferences(prefs);
1044 	}
1045 }
1046 
1047 void
on_view_language(GtkSourceLanguage * language)1048 Gobby::Window::on_view_language(GtkSourceLanguage* language)
1049 {
1050 	// Set language of current document
1051 	DocWindow* doc = get_current_document();
1052 	if(doc == NULL)
1053 	{
1054 		throw std::logic_error(
1055 			"Gobby::Window::on_view_language:\n"
1056 			"No window opened"
1057 		);
1058 	}
1059 
1060 	doc->set_language(language);
1061 }
1062 
on_window_chat()1063 void Gobby::Window::on_window_chat()
1064 {
1065 	if(m_header.action_window_chat->get_active() )
1066 	{
1067 		m_frame_chat.show_all();
1068 	}
1069 	else
1070 	{
1071 		m_frame_chat.hide_all();
1072 	}
1073 }
1074 
on_quit()1075 void Gobby::Window::on_quit()
1076 {
1077 	if(on_delete_event(NULL) == false)
1078 	{
1079 		// Quit session
1080 		if(m_buffer.get() != NULL && m_buffer->is_open() )
1081 			obby_end();
1082 		// End program
1083 		Gtk::Main::quit();
1084 	}
1085 }
1086 
on_obby_close()1087 void Gobby::Window::on_obby_close()
1088 {
1089 	display_error(_("Connection lost"));
1090 	on_session_quit();
1091 }
1092 
1093 /*void Gobby::Window::on_obby_encrypted()
1094 {
1095 	display_error("Connection now encrypted");
1096 }*/
1097 
on_obby_user_join(const obby::user & user)1098 void Gobby::Window::on_obby_user_join(const obby::user& user)
1099 {
1100 	// Tell user join to components
1101 	m_folder.obby_user_join(user);
1102 	m_userlist.obby_user_join(user);
1103 	m_documentlist.obby_user_join(user);
1104 	m_chat.obby_user_join(user);
1105 	m_statusbar.obby_user_join(user);
1106 }
1107 
on_obby_user_part(const obby::user & user)1108 void Gobby::Window::on_obby_user_part(const obby::user& user)
1109 {
1110 	// Tell user part to components
1111 	m_folder.obby_user_part(user);
1112 	m_userlist.obby_user_part(user);
1113 	m_documentlist.obby_user_part(user);
1114 	m_chat.obby_user_part(user);
1115 	m_statusbar.obby_user_part(user);
1116 }
1117 
on_obby_user_colour(const obby::user & user)1118 void Gobby::Window::on_obby_user_colour(const obby::user& user)
1119 {
1120 	m_userlist.obby_user_colour(user);
1121 	m_documentlist.obby_user_colour(user);
1122 	m_folder.obby_user_colour(user);
1123 }
1124 
on_obby_user_colour_failed()1125 void Gobby::Window::on_obby_user_colour_failed()
1126 {
1127 	display_error(_("Color change failed: Color already in use") );
1128 }
1129 
on_obby_document_insert(DocumentInfo & document)1130 void Gobby::Window::on_obby_document_insert(DocumentInfo& document)
1131 {
1132 	LocalDocumentInfo& local_doc =
1133 		dynamic_cast<LocalDocumentInfo&>(document);
1134 
1135 	m_folder.obby_document_insert(local_doc);
1136 	m_userlist.obby_document_insert(local_doc);
1137 	m_documentlist.obby_document_insert(local_doc);
1138 	m_chat.obby_document_insert(local_doc);
1139 	m_statusbar.obby_document_insert(local_doc);
1140 }
1141 
on_obby_document_remove(DocumentInfo & document)1142 void Gobby::Window::on_obby_document_remove(DocumentInfo& document)
1143 {
1144 	LocalDocumentInfo& local_doc =
1145 		dynamic_cast<LocalDocumentInfo&>(document);
1146 
1147 	m_folder.obby_document_remove(local_doc);
1148 	m_userlist.obby_document_remove(local_doc);
1149 	m_documentlist.obby_document_remove(local_doc);
1150 	m_chat.obby_document_remove(local_doc);
1151 	m_statusbar.obby_document_remove(local_doc);
1152 }
1153 
on_ipc_file(const std::string & file)1154 void Gobby::Window::on_ipc_file(const std::string& file)
1155 {
1156 	// Open local file directly if buffer is open
1157 	if(m_buffer.get() != NULL && m_buffer->is_open() )
1158 	{
1159 		open_local_file(file, EncodingSelector::AUTO_DETECT);
1160 		return;
1161 	}
1162 
1163 	// Otherwise, push the file back into the file queue.
1164 	bool was_empty = m_file_queue.empty();
1165 	m_file_queue.push(file);
1166 
1167 	// If the file queue is empty, open a new session. The queue will
1168 	// be cleared either if the session has finished (either with success
1169 	// or not).
1170 
1171 	// TODO: Find a better condition for when the session is currently
1172 	// being opened. Checking whether the file queue is empty is not
1173 	// good because the user might manually open a session while an
1174 	// IPC file request comes in...
1175 	if(was_empty)
1176 	{
1177 		session_open(false);
1178 	}
1179 }
1180 
get_current_document()1181 Gobby::DocWindow* Gobby::Window::get_current_document()
1182 {
1183 	if(m_folder.get_n_pages() == 0) return NULL;
1184 
1185 	Widget* page = m_folder.get_nth_page(m_folder.get_current_page() );
1186 	return static_cast<DocWindow*>(page);
1187 }
1188 
apply_preferences()1189 void Gobby::Window::apply_preferences()
1190 {
1191 	m_header.get_toolbar().set_toolbar_style(
1192 		m_preferences.appearance.toolbar_show);
1193 }
1194 
update_title_bar()1195 void Gobby::Window::update_title_bar()
1196 {
1197 	// No document
1198 	if(m_folder.get_n_pages() == 0)
1199 	{
1200 		set_title("Gobby");
1201 		return;
1202 	}
1203 
1204 	// Get currently active document
1205 	const DocWindow& window = *get_current_document();
1206 	// Get title of current document
1207 	const Glib::ustring& file = window.get_info().get_suffixed_title();
1208 	// Get path of current document
1209 	Glib::ustring path = m_document_settings.get_path(window.get_info() );
1210 
1211 	// Show path in title, if we know it
1212 	if(!path.empty() )
1213 	{
1214 		// Replace home dir by ~
1215 		Glib::ustring home = Glib::get_home_dir();
1216 		if(path.compare(0, home.length(), home) == 0)
1217 			path.replace(0, home.length(), "~");
1218 
1219 		// Set title with file and path
1220 		obby::format_string title_str("%0% (%1%) - Gobby");
1221 		title_str << file << Glib::path_get_dirname(path);
1222 		set_title(title_str.str() );
1223 	}
1224 	else
1225 	{
1226 		// Path not known: Set title with file only
1227 		obby::format_string title_str("%0% - Gobby");
1228 		title_str << file;
1229 		set_title(title_str.str() );
1230 	}
1231 }
1232 
1233 namespace
1234 {
1235 	// convert2unix converts a given string from any special line endings
1236 	// (DOS or old-style Macintosh) to Unix line endings. It does no
1237 	// book-keeping about the encountered endings but ensures that no
1238 	// CR characters are left in the string.
convert2unix(std::string & str)1239 	void convert2unix(std::string& str)
1240 	{
1241 		for(std::string::size_type i = 0; i < str.length(); ++ i)
1242 			// Convert DOS CRLF to a single LF
1243 			if(i < str.length() - 1 &&
1244 			   str[i] == '\r' && str[i+1] == '\n')
1245 				str.erase(i, 1);
1246 			// Convert Macintosh CR to LF
1247 			else if(str[i] == '\r')
1248 				str[i] = '\n';
1249 	}
1250 }
1251 
session_join(bool initial_dialog)1252 bool Gobby::Window::session_join(bool initial_dialog)
1253 {
1254 	if(m_buffer.get() && m_buffer->is_open() )
1255 	{
1256 		throw std::logic_error(
1257 			"Gobby::Window::session_join:\n"
1258 			"Buffer is already open"
1259 		);
1260 	}
1261 
1262 	if(m_join_dlg.get() == NULL)
1263 	{
1264 #ifndef WITH_ZEROCONF
1265 		m_join_dlg.reset(
1266 			new JoinDialog(*this, m_config.get_root()["session"])
1267 		);
1268 #else
1269 		m_join_dlg.reset(
1270 			new JoinDialog(
1271 				*this,
1272 				m_config.get_root()["session"],
1273 				m_zeroconf.get()
1274 			)
1275 		);
1276 #endif
1277 	}
1278 
1279 	int response = Gtk::RESPONSE_OK;
1280 	if(initial_dialog) response = m_join_dlg->run();
1281 
1282 	while(response == Gtk::RESPONSE_OK)
1283 	{
1284 		// Read settings
1285 		Glib::ustring host = m_join_dlg->get_host();
1286 		unsigned int port = m_join_dlg->get_port();
1287 		const net6::address* addr = m_join_dlg->get_address();
1288 		Glib::ustring name = m_join_dlg->get_name();
1289 		Gdk::Color color = m_join_dlg->get_color();
1290 
1291 		if(session_join_impl(host, port, addr, name, color) )
1292 			break;
1293 		else
1294 			response = m_join_dlg->run();
1295 	}
1296 
1297 	m_join_dlg->hide();
1298 	return (m_buffer.get() && m_buffer->is_open() );
1299 }
1300 
session_open(bool initial_dialog)1301 bool Gobby::Window::session_open(bool initial_dialog)
1302 {
1303 	if(m_buffer.get() && m_buffer->is_open() )
1304 	{
1305 		throw std::logic_error(
1306 			"Gobby::Window::session_open:\n"
1307 			"Buffer is already open"
1308 		);
1309 	}
1310 
1311 	if(m_host_dlg.get() == NULL)
1312 	{
1313 		m_host_dlg.reset(
1314 			new HostDialog(*this, m_config.get_root()["session"])
1315 		);
1316 	}
1317 
1318 	int response = Gtk::RESPONSE_OK;
1319 	if(initial_dialog) response = m_host_dlg->run();
1320 
1321 	while(response == Gtk::RESPONSE_OK)
1322 	{
1323 		// Read setting
1324 		unsigned int port = m_host_dlg->get_port();
1325 		Glib::ustring name = m_host_dlg->get_name();
1326 		Gdk::Color color = m_host_dlg->get_color();
1327 		Glib::ustring password = m_host_dlg->get_password();
1328 		Glib::ustring session = m_host_dlg->get_session();
1329 
1330 		if(session_open_impl(port, name, color, password, session) )
1331 			break;
1332 		else
1333 			response = m_host_dlg->run();
1334 	}
1335 
1336 	// Process file queue (files that have been queued for opening
1337 	// after session creation).
1338 	m_host_dlg->hide();
1339 	while(!m_file_queue.empty() )
1340 	{
1341 		std::string str = m_file_queue.front();
1342 		m_file_queue.pop();
1343 
1344 		if(m_buffer.get() && m_buffer->is_open() )
1345 			open_local_file(str, EncodingSelector::AUTO_DETECT);
1346 	}
1347 
1348 	return (m_buffer.get() && m_buffer->is_open() );
1349 }
1350 
session_join_impl(const Glib::ustring & host,unsigned int port,const net6::address * addr,const Glib::ustring & name,const Gdk::Color & color)1351 bool Gobby::Window::session_join_impl(const Glib::ustring& host,
1352                                       unsigned int port,
1353 				      const net6::address* addr,
1354                                       const Glib::ustring& name,
1355                                       const Gdk::Color& color)
1356 {
1357 	JoinProgressDialog prgdlg(
1358 		*this,
1359 		m_config.get_root()["session"],
1360 		host,
1361 		port,
1362 		addr,
1363 		name,
1364 		color
1365 	);
1366 
1367 	if(prgdlg.run() == Gtk::RESPONSE_OK)
1368 	{
1369 		prgdlg.hide();
1370 
1371 		// Get buffer
1372 		std::auto_ptr<ClientBuffer> buffer = prgdlg.get_buffer();
1373 
1374 		buffer->set_enable_keepalives(true);
1375 
1376 		buffer->close_event().connect(
1377 			sigc::mem_fun(*this, &Window::on_obby_close) );
1378 
1379 		obby::format_string str(_("Connected to %0%:%1%") );
1380 		str << host << port;
1381 		m_statusbar.update_connection(str.str() );
1382 
1383 		// Start session
1384 		m_buffer = buffer;
1385 		obby_start();
1386 
1387 		// Session is open, no need to reshow join dialog
1388 		return true;
1389 	}
1390 	else
1391 	{
1392 		return false;
1393 	}
1394 }
1395 
session_open_impl(unsigned int port,const Glib::ustring & name,const Gdk::Color & color,const Glib::ustring & password,const Glib::ustring & session)1396 bool Gobby::Window::session_open_impl(unsigned int port,
1397                                       const Glib::ustring& name,
1398                                       const Gdk::Color& color,
1399                                       const Glib::ustring& password,
1400                                       const Glib::ustring& session)
1401 {
1402 	// Set up host with hostprogressdialog
1403 	HostProgressDialog prgdlg(*this, m_config, port, name, color, session);
1404 
1405 	if(prgdlg.run() == Gtk::RESPONSE_OK)
1406 	{
1407 		prgdlg.hide();
1408 
1409 		// Get buffer
1410 		std::auto_ptr<HostBuffer> buffer =
1411 			prgdlg.get_buffer();
1412 
1413 		// Set password
1414 		buffer->set_global_password(password);
1415 		buffer->set_enable_keepalives(true);
1416 #ifdef WITH_ZEROCONF
1417 		// Publish the newly created session via Zeroconf
1418 		// if Howl is not deactivated
1419 		if(m_zeroconf.get() )
1420 			m_zeroconf->publish(name, port);
1421 #endif
1422 
1423 		obby::format_string str(_("Serving on port %0%") );
1424 		str << port;
1425 		m_statusbar.update_connection(str.str() );
1426 
1427 		m_buffer = buffer;
1428 
1429 		// Start session
1430 		obby_start();
1431 		// Remember session file
1432 		m_prev_session = Glib::filename_from_utf8(session);
1433 		// Session is open, no need to reshow host dialog
1434 		return true;
1435 	}
1436 	else
1437 	{
1438 		// Session opening did not succeed
1439 		return false;
1440 	}
1441 }
1442 
open_local_file(const Glib::ustring & file,const std::string & encoding)1443 void Gobby::Window::open_local_file(const Glib::ustring& file,
1444                                     const std::string& encoding)
1445 {
1446 	try
1447 	{
1448 		// Set local file path for the document_insert callback
1449 		m_local_file_path = file;
1450 		m_local_encoding = encoding;
1451 
1452 		std::string utf8_content;
1453 		if(encoding == EncodingSelector::AUTO_DETECT)
1454 		{
1455 			std::string detected_encoding;
1456 
1457 			utf8_content = Encoding::convert_to_utf8(
1458 					Glib::file_get_contents(file),
1459 					detected_encoding
1460 			);
1461 
1462 			m_local_encoding = detected_encoding;
1463 		}
1464 		else
1465 		{
1466 			utf8_content = Glib::convert(
1467 				Glib::file_get_contents(file),
1468 				"UTF-8",
1469 				encoding
1470 			);
1471 		}
1472 
1473 		convert2unix(utf8_content);
1474 
1475 		m_buffer->document_create(
1476 			Glib::filename_to_utf8(Glib::path_get_basename(file)),
1477 			"UTF-8", utf8_content
1478 		);
1479 
1480 		// Clear local path
1481 		m_local_file_path.clear();
1482 		m_local_encoding.clear();
1483 	}
1484 	catch(Glib::Exception& e)
1485 	{
1486 		// Show errors while opening the file (e.g. if it doesn't exist)
1487 		display_error(e.what() );
1488 	}
1489 }
1490 
save_local_file(DocWindow & doc,const Glib::ustring & file,const std::string & encoding)1491 void Gobby::Window::save_local_file(DocWindow& doc,
1492                                     const Glib::ustring& file,
1493                                     const std::string& encoding)
1494 {
1495 	try
1496 	{
1497 		Glib::RefPtr<Glib::IOChannel> channel = Glib::IOChannel::create_from_file(file, "w");
1498 		channel->set_encoding("");
1499 
1500 		// Save content into file
1501 		std::string conv_content = doc.get_content().raw();
1502 		if(encoding != "UTF-8")
1503 		{
1504 			conv_content = Glib::convert(
1505 				conv_content,
1506 				encoding,
1507 				"UTF-8"
1508 			);
1509 		}
1510 
1511 		channel->write(conv_content);
1512 		channel->close();
1513 
1514 		m_document_settings.set_path(doc.get_info(), file);
1515 
1516 		m_document_settings.set_original_encoding(
1517 			doc.get_info(),
1518 			encoding
1519 		);
1520 
1521 		// Update title bar according to new path
1522 		update_title_bar();
1523 		// Unset modifified flag
1524 		gtk_text_buffer_set_modified(
1525 			GTK_TEXT_BUFFER(doc.get_document().get_buffer()),
1526 			FALSE);
1527 	}
1528 	catch(Glib::Error& e)
1529 	{
1530 		display_error(e.what() );
1531 	}
1532 	catch(std::exception& e)
1533 	{
1534 		display_error(e.what() );
1535 	}
1536 }
1537 
close_document(DocWindow & window)1538 void Gobby::Window::close_document(DocWindow& window)
1539 {
1540 	/* Cannot unsubscribe when not subscribed (e.g. unsubscription request
1541 	 * has already been sent. */
1542 	if(window.get_info().get_subscription_state() !=
1543 	   Gobby::LocalDocumentInfo::SUBSCRIBED)
1544 	{
1545 		return;
1546 	}
1547 
1548 	// Check for the document being modified
1549 	if(window.get_modified() )
1550 	{
1551 		// Setup confirmation strings
1552 		obby::format_string primary_str(
1553 			_("Save changes to document \"%0%\" before closing?")
1554 		);
1555 
1556 		// TODO: Tell that resubscription is not possible when the
1557 		// session is closed (unless you are host).
1558 
1559 		std::string secondary_str;
1560 		if(m_buffer.get() != NULL && m_buffer->is_open() )
1561 		{
1562 			secondary_str = _(
1563 				"If you don't save, changes will be "
1564 				"discarded, but may still be retrieved if "
1565 				"you re-subscribe to the document as long "
1566 				"as the session remains open."
1567 			);
1568 		}
1569 		else
1570 		{
1571 			secondary_str = _(
1572 				"If you don't save, changes will be "
1573 				"discarded."
1574 			);
1575 		}
1576 
1577 		primary_str << window.get_info().get_suffixed_title();
1578 
1579 		// Setup dialog
1580 		Gtk::MessageDialog dlg(*this, primary_str.str(), false,
1581 			Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, true);
1582 
1583 		// Set secondary text
1584 		dlg.set_secondary_text(secondary_str);
1585 
1586 		// Add button to allow the user to save the dialog
1587 		dlg.add_button(_("Close without saving"), Gtk::RESPONSE_REJECT);
1588 		dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
1589 		dlg.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT)->
1590 			grab_focus();
1591 
1592 		// Show the dialog
1593 		int result = dlg.run();
1594 		// Hide it because we cannot back up to it if a later dialog
1595 		// is cancelled.
1596 		dlg.hide();
1597 
1598 		switch(result)
1599 		{
1600 		case Gtk::RESPONSE_REJECT:
1601 			/* Close the document */
1602 			break;
1603 		case Gtk::RESPONSE_ACCEPT:
1604 			/* Save the document before closing it */
1605 			m_folder.set_current_page(m_folder.page_num(window) );
1606 			if(!handle_document_save() )
1607 				return;
1608 			break;
1609 		case Gtk::RESPONSE_CANCEL:
1610 		case Gtk::RESPONSE_DELETE_EVENT:
1611 			/* Do not close the document */
1612 			return;
1613 			break;
1614 		default:
1615 			throw std::logic_error("Gobby::Window::close_document");
1616 			break;
1617 		}
1618 	}
1619 
1620 	window.get_info().unsubscribe();
1621 }
1622 
display_error(const Glib::ustring & message,const Gtk::MessageType type)1623 void Gobby::Window::display_error(const Glib::ustring& message,
1624                                   const Gtk::MessageType type)
1625 {
1626 	Gtk::MessageDialog dlg(*this, message, false, type,
1627 	                       Gtk::BUTTONS_OK, true);
1628 	dlg.run();
1629 }
1630