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