1 /* gobby - A GTKmm driven libobby client
2 * Copyright (C) 2005 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 "features.hpp"
20
21 #include <glibmm/pattern.h>
22 #include <gtkmm/textview.h>
23
24 #ifdef WITH_GTKSOURCEVIEW2
25 # include <gtksourceview/gtksourcebuffer.h>
26 #endif
27
28 #ifdef WITH_GTKSPELL
29 # include <obby/format_string.hpp>
30 # include <gtkmm/messagedialog.h>
31 # include <gtkspell/gtkspell.h>
32 #endif
33
34 #include "common.hpp"
35 #include "preferences.hpp"
36 #include "docwindow.hpp"
37
38 namespace
39 {
wrap_mode_from_preferences(const Gobby::Preferences & pref)40 GtkWrapMode wrap_mode_from_preferences(const Gobby::Preferences& pref)
41 {
42 if(pref.view.wrap_text)
43 {
44 if(pref.view.wrap_words)
45 return GTK_WRAP_CHAR;
46 else
47 return GTK_WRAP_WORD;
48 }
49 else
50 {
51 return GTK_WRAP_NONE;
52 }
53 }
54 }
55
DocWindow(LocalDocumentInfo & info,const Preferences & preferences)56 Gobby::DocWindow::DocWindow(LocalDocumentInfo& info,
57 const Preferences& preferences):
58 m_view(GTK_SOURCE_VIEW(gtk_source_view_new())),
59 m_info(info), m_doc(info.get_content() ),
60 m_preferences(preferences), /*m_editing(false),*/
61 m_title(info.get_title() ),
62 m_scrolly(0.0),
63 m_scroll_restore(false)
64 {
65 if(!info.is_subscribed() )
66 {
67 throw std::logic_error(
68 "Gobby::DocWindow::DocWindow:\n"
69 "Local user is not subscribed"
70 );
71 }
72
73 GtkSourceBuffer* buffer = m_doc.get_buffer();
74 gtk_text_view_set_buffer(GTK_TEXT_VIEW(m_view), GTK_TEXT_BUFFER(buffer));
75
76 Glib::RefPtr<Gtk::TextBuffer> cpp_buffer =
77 Glib::wrap(GTK_TEXT_BUFFER(buffer), true);
78
79 #ifdef WITH_GTKSOURCEVIEW2
80 // Set source language by filename
81 gtk_source_buffer_set_highlight_syntax(buffer, FALSE);
82 #else
83 gtk_source_buffer_set_highlight(buffer, FALSE);
84 #endif
85
86 // Enable indent-on-tab
87 gtk_source_view_set_indent_on_tab(m_view, TRUE);
88
89 for(Preferences::FileList::iterator iter = preferences.files.begin();
90 iter != preferences.files.end();
91 ++ iter)
92 {
93 Glib::PatternSpec spec(iter.pattern());
94 if(spec.match(info.get_title()) )
95 {
96 gtk_source_buffer_set_language(buffer, iter.language());
97 #ifdef WITH_GTKSOURCEVIEW2
98 gtk_source_buffer_set_highlight_syntax(buffer, TRUE);
99 #else
100 gtk_source_buffer_set_highlight(buffer, TRUE);
101 #endif
102 }
103 }
104
105 #ifdef WITH_GTKSOURCEVIEW2
106 // Set a theme so we see anything.
107 // TODO: This should be temporary code until gtksourceview2 sets a default
108 // theme.
109 /* GtkSourceStyleManager* sm = gtk_source_style_manager_new();
110 GtkSourceStyleScheme* scheme = gtk_source_style_manager_get_scheme(sm, "gvim");
111 gtk_source_buffer_set_style_scheme(buffer, scheme);
112 g_object_unref(G_OBJECT(sm));*/
113 #endif
114
115 cpp_buffer->signal_mark_set().connect(
116 sigc::mem_fun(*this, &DocWindow::on_mark_set)
117 );
118
119 cpp_buffer->signal_changed().connect(
120 sigc::mem_fun(*this, &DocWindow::on_changed)
121 );
122
123 m_doc.local_insert_event().connect(
124 sigc::mem_fun(*this, &DocWindow::on_local_insert)
125 );
126
127 m_doc.local_erase_event().connect(
128 sigc::mem_fun(*this, &DocWindow::on_local_erase)
129 );
130
131 m_doc.remote_insert_before_event().connect(
132 sigc::mem_fun(*this, &DocWindow::on_remote_insert_before)
133 );
134
135 m_doc.remote_erase_before_event().connect(
136 sigc::mem_fun(*this, &DocWindow::on_remote_erase_before)
137 );
138
139 m_doc.remote_insert_after_event().connect(
140 sigc::mem_fun(*this, &DocWindow::on_remote_insert_after)
141 );
142
143 m_doc.remote_erase_after_event().connect(
144 sigc::mem_fun(*this, &DocWindow::on_remote_erase_after)
145 );
146
147 apply_preferences();
148 gtk_text_buffer_set_modified(GTK_TEXT_BUFFER(buffer), !m_doc.empty());
149
150 set_shadow_type(Gtk::SHADOW_IN);
151 set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
152 gtk_container_add(GTK_CONTAINER(gobj()), GTK_WIDGET(m_view));
153
154 #ifdef WITH_GTKSPELL
155 // Set up spell checking.
156 GError *error = NULL;
157 if(gtkspell_new_attach(GTK_TEXT_VIEW(m_view), NULL, &error) == NULL)
158 {
159 obby::format_string str(_("GtkSpell error: %0%") );
160 str << error->message;
161 g_error_free(error);
162
163 // Initialization failed, show error message.
164 Gtk::MessageDialog dlg(
165 str.str(),
166 false,
167 Gtk::MESSAGE_ERROR,
168 Gtk::BUTTONS_CLOSE,
169 true);
170 dlg.run();
171 }
172 #endif
173 }
174
get_cursor_position(unsigned int & row,unsigned int & col)175 void Gobby::DocWindow::get_cursor_position(unsigned int& row,
176 unsigned int& col)
177 {
178 Glib::RefPtr<Gtk::TextBuffer> cpp_buffer = Glib::wrap(
179 gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view)), true);
180
181 Glib::RefPtr<Gtk::TextMark> mark = cpp_buffer->get_insert();
182
183 // Gtk::TextBuffer::Mark::get_iter is not const. Why not? It prevents
184 // this function from being const.
185 Gtk::TextBuffer::iterator iter = mark->get_iter();
186
187 // Row is trivial
188 row = iter.get_line(); col = 0;
189 int chars = iter.get_line_offset();
190
191 // Tab characters expand to more than one column
192 unsigned int tabs = m_preferences.editor.tab_width;
193 for(iter.set_line_offset(0); iter.get_line_offset() < chars; ++ iter)
194 {
195 unsigned int width = 1;
196 if(*iter == '\t')
197 {
198 width = (tabs - iter.get_line_offset() % tabs) % tabs;
199 if(width == 0) width = tabs;
200 }
201
202 col += width;
203 }
204 }
205
set_selection(const Gtk::TextIter & begin,const Gtk::TextIter & end)206 void Gobby::DocWindow::set_selection(const Gtk::TextIter& begin,
207 const Gtk::TextIter& end)
208 {
209 gtk_text_buffer_select_range(
210 gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view)),
211 begin.gobj(),
212 end.gobj()
213 );
214
215 gtk_text_view_scroll_to_mark(
216 GTK_TEXT_VIEW(m_view),
217 gtk_text_buffer_get_insert(gtk_text_view_get_buffer(
218 GTK_TEXT_VIEW(m_view))),
219 0.1, FALSE, 0.0, 0.0);
220 }
221
disable()222 void Gobby::DocWindow::disable()
223 {
224 gtk_widget_set_sensitive(GTK_WIDGET(m_view), FALSE);
225 }
226
get_selected_text() const227 Glib::ustring Gobby::DocWindow::get_selected_text() const
228 {
229 GtkTextIter start, end;
230 gtk_text_buffer_get_selection_bounds(
231 gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view)), &start, &end);
232
233 Gtk::TextIter start_cpp(&start), end_cpp(&end);
234 return start_cpp.get_slice(end_cpp);
235 }
236
get_title() const237 const Glib::ustring& Gobby::DocWindow::get_title() const
238 {
239 return m_title;
240 }
241
get_modified() const242 bool Gobby::DocWindow::get_modified() const
243 {
244 return gtk_text_buffer_get_modified(
245 gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view))) == TRUE;
246 }
247
grab_focus()248 void Gobby::DocWindow::grab_focus()
249 {
250 Gtk::ScrolledWindow::grab_focus();
251 gtk_widget_grab_focus(GTK_WIDGET(m_view));
252 }
253
get_language() const254 GtkSourceLanguage* Gobby::DocWindow::get_language() const
255 {
256 return gtk_source_buffer_get_language(GTK_SOURCE_BUFFER(
257 gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view))));
258 }
259
set_language(GtkSourceLanguage * language)260 void Gobby::DocWindow::set_language(GtkSourceLanguage* language)
261 {
262 GtkSourceBuffer* buffer = GTK_SOURCE_BUFFER(
263 gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view)));
264
265 gtk_source_buffer_set_language(buffer, language);
266
267 #ifdef WITH_GTKSOURCEVIEW2
268 gtk_source_buffer_set_highlight_syntax(buffer, language != NULL);
269 #else
270 gtk_source_buffer_set_highlight(buffer, language != NULL);
271 #endif
272
273 m_signal_language_changed.emit();
274 }
275
get_preferences() const276 const Gobby::Preferences& Gobby::DocWindow::get_preferences() const
277 {
278 return m_preferences;
279 }
280
set_preferences(const Preferences & preferences)281 void Gobby::DocWindow::set_preferences(const Preferences& preferences)
282 {
283 m_preferences = preferences;
284 apply_preferences();
285 }
286
get_content() const287 Glib::ustring Gobby::DocWindow::get_content() const
288 {
289 Glib::RefPtr<Gtk::TextBuffer> buffer = Glib::wrap(
290 gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view)), true);
291
292 return buffer->get_text();
293 }
294
295 Gobby::DocWindow::signal_cursor_moved_type
cursor_moved_event() const296 Gobby::DocWindow::cursor_moved_event() const
297 {
298 return m_signal_cursor_moved;
299 }
300
301 Gobby::DocWindow::signal_content_changed_type
content_changed_event() const302 Gobby::DocWindow::content_changed_event() const
303 {
304 return m_signal_content_changed;
305 }
306
307 Gobby::DocWindow::signal_language_changed_type
language_changed_event() const308 Gobby::DocWindow::language_changed_event() const
309 {
310 return m_signal_language_changed;
311 }
312
get_info() const313 const Gobby::LocalDocumentInfo& Gobby::DocWindow::get_info() const
314 {
315 return m_info;
316 }
317
get_info()318 Gobby::LocalDocumentInfo& Gobby::DocWindow::get_info()
319 {
320 return m_info;
321 }
322
get_document() const323 const Gobby::Document& Gobby::DocWindow::get_document() const
324 {
325 return m_doc;
326 }
327
on_mark_set(const Gtk::TextIter & location,const Glib::RefPtr<Gtk::TextMark> & mark)328 void Gobby::DocWindow::on_mark_set(const Gtk::TextIter& location,
329 const Glib::RefPtr<Gtk::TextMark>& mark)
330 {
331 // Mark was deleted
332 if(!mark) return;
333
334 if(mark->gobj() == gtk_text_buffer_get_insert(
335 gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view))))
336 {
337 m_signal_cursor_moved.emit();
338 }
339 }
340
on_changed()341 void Gobby::DocWindow::on_changed()
342 {
343 // Cursor may have moved.
344 // TODO: Check if the cursor really moved
345 m_signal_cursor_moved.emit();
346 m_signal_content_changed.emit();
347 }
348
on_local_insert(obby::position pos,const std::string & error)349 void Gobby::DocWindow::on_local_insert(obby::position pos,
350 const std::string& error)
351 {
352 m_info.insert(pos, error);
353 }
354
on_local_erase(obby::position pos,obby::position len)355 void Gobby::DocWindow::on_local_erase(obby::position pos,
356 obby::position len)
357 {
358 m_info.erase(pos, len);
359 }
360
on_remote_insert_before(obby::position,const std::string & text)361 void Gobby::DocWindow::on_remote_insert_before(obby::position,
362 const std::string& text)
363 {
364 store_scroll();
365 }
366
on_remote_erase_before(obby::position pos,obby::position len)367 void Gobby::DocWindow::on_remote_erase_before(obby::position pos,
368 obby::position len)
369 {
370 store_scroll();
371 }
372
on_remote_insert_after(obby::position,const std::string & text)373 void Gobby::DocWindow::on_remote_insert_after(obby::position,
374 const std::string& text)
375 {
376 restore_scroll();
377 }
378
on_remote_erase_after(obby::position pos,obby::position len)379 void Gobby::DocWindow::on_remote_erase_after(obby::position pos,
380 obby::position len)
381 {
382 restore_scroll();
383 }
384
apply_preferences()385 void Gobby::DocWindow::apply_preferences()
386 {
387 GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(m_view));
388
389 #ifdef WITH_GTKSOURCEVIEW2
390 gtk_source_view_set_tab_width(GTK_SOURCE_VIEW(m_view),
391 m_preferences.editor.tab_width);
392 #else
393 gtk_source_view_set_tabs_width(GTK_SOURCE_VIEW(m_view),
394 m_preferences.editor.tab_width);
395 #endif
396
397 gtk_source_view_set_insert_spaces_instead_of_tabs(GTK_SOURCE_VIEW(m_view),
398 m_preferences.editor.tab_spaces);
399 gtk_source_view_set_auto_indent(GTK_SOURCE_VIEW(m_view),
400 m_preferences.editor.indentation_auto);
401 #ifdef WITH_GTKSOURCEVIEW2
402 gtk_source_view_set_smart_home_end(GTK_SOURCE_VIEW(m_view),
403 m_preferences.editor.homeend_smart ?
404 GTK_SOURCE_SMART_HOME_END_ALWAYS :
405 GTK_SOURCE_SMART_HOME_END_DISABLED);
406 #else
407 gtk_source_view_set_smart_home_end(GTK_SOURCE_VIEW(m_view),
408 m_preferences.editor.homeend_smart);
409 #endif
410
411 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(m_view),
412 wrap_mode_from_preferences(m_preferences));
413 gtk_source_view_set_show_line_numbers(GTK_SOURCE_VIEW(m_view),
414 m_preferences.view.linenum_display);
415 gtk_source_view_set_highlight_current_line(GTK_SOURCE_VIEW(m_view),
416 m_preferences.view.curline_highlight);
417 #ifdef WITH_GTKSOURCEVIEW2
418 gtk_source_view_set_show_right_margin(GTK_SOURCE_VIEW(m_view),
419 m_preferences.view.margin_display);
420 gtk_source_view_set_right_margin_position(GTK_SOURCE_VIEW(m_view),
421 m_preferences.view.margin_pos);
422 gtk_source_buffer_set_highlight_matching_brackets(GTK_SOURCE_BUFFER(buffer),
423 m_preferences.view.bracket_highlight);
424 #else
425 gtk_source_view_set_show_margin(GTK_SOURCE_VIEW(m_view),
426 m_preferences.view.margin_display);
427 gtk_source_view_set_margin(GTK_SOURCE_VIEW(m_view),
428 m_preferences.view.margin_pos);
429 gtk_source_buffer_set_check_brackets(GTK_SOURCE_BUFFER(buffer),
430 m_preferences.view.bracket_highlight);
431 #endif
432
433 gtk_widget_modify_font(GTK_WIDGET(m_view), m_preferences.font.desc.gobj());
434
435 // Cursor position may have changed because of new tab width
436 // TODO: Only emit if the position really changed
437 m_signal_cursor_moved.emit();
438 }
439
store_scroll()440 void Gobby::DocWindow::store_scroll()
441 {
442 Gdk::Rectangle curs_rect;
443 int x, y;
444
445 Gtk::TextView* cpp_view = Glib::wrap(GTK_TEXT_VIEW(m_view));
446 cpp_view->get_iter_location(cpp_view->get_buffer()->get_insert()->get_iter(), curs_rect);
447 cpp_view->buffer_to_window_coords(
448 Gtk::TEXT_WINDOW_WIDGET,
449 curs_rect.get_x(), curs_rect.get_y(),
450 x, y);
451
452 m_scrolly = y / static_cast<double>((cpp_view->get_height() - curs_rect.get_height()));
453 m_scroll_restore = true;
454 }
455
restore_scroll()456 void Gobby::DocWindow::restore_scroll()
457 {
458 if(m_scroll_restore)
459 {
460 Gtk::TextView* cpp_view = Glib::wrap(GTK_TEXT_VIEW(m_view));
461 if(m_scrolly >= 0.0 && m_scrolly <= 1.0)
462 cpp_view->scroll_to(
463 cpp_view->get_buffer()->get_insert(),
464 0, 0,
465 m_scrolly);
466
467 m_scroll_restore = false;
468 }
469 }
470
471