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 <cstring>
20 #include <stdexcept>
21 #include <gtkmm/stock.h>
22 #include <gtkmm/messagedialog.h>
23 
24 #include <obby/format_string.hpp>
25 
26 #include "common.hpp"
27 #include "preferencesdialog.hpp"
28 
29 namespace
30 {
lang_sort(gconstpointer first,gconstpointer second)31 	gint lang_sort(gconstpointer first, gconstpointer second)
32 	{
33 		return strcmp(
34 			gtk_source_language_get_name(GTK_SOURCE_LANGUAGE(first)),
35 			gtk_source_language_get_name(GTK_SOURCE_LANGUAGE(second))
36 			);
37 	}
38 
rownum_to_toolstyle(int rownum)39 	Gtk::ToolbarStyle rownum_to_toolstyle(int rownum)
40 	{
41 		switch(rownum)
42 		{
43 		case 0: return Gtk::TOOLBAR_TEXT;
44 		case 1: return Gtk::TOOLBAR_ICONS;
45 		case 3: return Gtk::TOOLBAR_BOTH_HORIZ;
46 		case 2: default: return Gtk::TOOLBAR_BOTH;
47 		}
48 	}
49 }
50 
Page()51 Gobby::PreferencesDialog::Page::Page():
52 	Gtk::Frame()
53 {
54 	// Remove shadow - use the frame just as container
55 	set_shadow_type(Gtk::SHADOW_NONE);
56 	set_border_width(10);
57 }
58 
59 #ifndef GTKMM_DISABLE_DEPRECATED
Editor(const Preferences & preferences,Gtk::Tooltips & tooltips)60 Gobby::PreferencesDialog::Editor::Editor(const Preferences& preferences,
61                                          Gtk::Tooltips& tooltips):
62 #else
63 Gobby::PreferencesDialog::Editor::Editor(const Preferences& preferences):
64 #endif
65 	m_frame_tab(_("Tab Stops") ),
66 	m_frame_indentation(_("Indentation") ),
67 	m_frame_homeend(_("Home/End behaviour") ),
68 	m_lbl_tab_width(_("Tab width:"), Gtk::ALIGN_RIGHT),
69 	m_btn_tab_spaces(_("Insert spaces instead of tabs") ),
70 	m_btn_indentation_auto(_("Enable automatic indentation") ),
71 	m_btn_homeend_smart(_("Smart home/end") )
72 {
73 	unsigned int tab_width = preferences.editor.tab_width;
74 	bool tab_spaces = preferences.editor.tab_spaces;
75 	bool indentation_auto = preferences.editor.indentation_auto;
76 	bool homeend_smart = preferences.editor.homeend_smart;
77 
78 	m_ent_tab_width.set_range(1, 8);
79 	m_ent_tab_width.set_value(tab_width);
80 	m_ent_tab_width.set_increments(1, 1);
81 
82 #ifndef GTKMM_DISABLE_DEPRECATED
83 	// TODO: Improve this description
84 	tooltips.set_tip(m_btn_homeend_smart,
85 		_("With this option enabled, Home/End keys move to first/last "
86 		  "character before going to the start/end of the line.") );
87 #endif
88 
89 	m_box_tab_width.set_spacing(5);
90 	m_box_tab_width.pack_start(m_lbl_tab_width, Gtk::PACK_SHRINK);
91 	m_box_tab_width.pack_start(m_ent_tab_width, Gtk::PACK_EXPAND_WIDGET);
92 
93 	m_btn_tab_spaces.set_active(tab_spaces);
94 	m_btn_indentation_auto.set_active(indentation_auto);
95 	m_btn_homeend_smart.set_active(homeend_smart);
96 
97 	m_box_tab.set_spacing(5);
98 	m_box_tab.set_border_width(5);
99 	m_box_tab.pack_start(m_box_tab_width, Gtk::PACK_SHRINK);
100 	m_box_tab.pack_start(m_btn_tab_spaces, Gtk::PACK_SHRINK);
101 
102 	m_box_indentation.set_spacing(5);
103 	m_box_indentation.set_border_width(5);
104 	m_box_indentation.pack_start(m_btn_indentation_auto, Gtk::PACK_SHRINK);
105 
106 	m_box_homeend.set_spacing(5);
107 	m_box_homeend.set_border_width(5);
108 	m_box_homeend.pack_start(m_btn_homeend_smart, Gtk::PACK_SHRINK);
109 
110 	m_frame_tab.add(m_box_tab);
111 	m_frame_indentation.add(m_box_indentation);
112 	m_frame_homeend.add(m_box_homeend);
113 
114 	m_box.set_spacing(5);
115 	m_box.pack_start(m_frame_tab, Gtk::PACK_SHRINK);
116 	m_box.pack_start(m_frame_indentation, Gtk::PACK_SHRINK);
117 	m_box.pack_start(m_frame_homeend, Gtk::PACK_SHRINK);
118 
119 	add(m_box);
120 }
121 
set(Preferences::Editor & editor) const122 void Gobby::PreferencesDialog::Editor::set(Preferences::Editor& editor) const
123 {
124 	editor.tab_width = m_ent_tab_width.get_value_as_int();
125 	editor.tab_spaces = m_btn_tab_spaces.get_active();
126 	editor.indentation_auto = m_btn_indentation_auto.get_active();
127 	editor.homeend_smart = m_btn_homeend_smart.get_active();
128 }
129 
View(const Preferences & preferences)130 Gobby::PreferencesDialog::View::View(const Preferences& preferences):
131 	m_frame_wrap(_("Text wrapping") ),
132 	m_frame_linenum(_("Line numbers") ),
133 	m_frame_curline(_("Current line") ),
134 	m_frame_margin(_("Right margin") ),
135 	m_frame_bracket(_("Bracket matching") ),
136 	m_btn_wrap_text(_("Enable text wrapping") ),
137 	m_btn_wrap_words(_("Do not split words over two lines") ),
138 	m_btn_linenum_display(_("Display line numbers") ),
139 	m_btn_curline_highlight(_("Highlight current line") ),
140 	m_btn_margin_display(_("Display right margin") ),
141 	m_lbl_margin_pos(_("Right margin at column:") ),
142 	m_btn_bracket_highlight(_("Highlight matching bracket") )
143 {
144 	bool wrap_text = preferences.view.wrap_text;
145 	bool wrap_words = preferences.view.wrap_words;
146 	bool linenum_display = preferences.view.linenum_display;
147 	bool curline_highlight = preferences.view.curline_highlight;
148 	bool margin_display = preferences.view.margin_display;
149 	unsigned int margin_pos = preferences.view.margin_pos;
150 	bool bracket_highlight = preferences.view.bracket_highlight;
151 
152 	m_btn_margin_display.signal_toggled().connect(
153 		sigc::mem_fun(*this, &View::on_margin_display_toggled) );
154 
155 	m_ent_margin_pos.set_range(1, 1024);
156 	m_ent_margin_pos.set_value(margin_pos);
157 	m_ent_margin_pos.set_increments(1, 16);
158 
159 	m_btn_wrap_text.set_active(wrap_text);
160 	m_btn_wrap_words.set_active(!wrap_words);
161 	m_btn_linenum_display.set_active(linenum_display);
162 	m_btn_curline_highlight.set_active(curline_highlight);
163 	m_btn_margin_display.set_active(margin_display);
164 	m_btn_bracket_highlight.set_active(bracket_highlight);
165 
166 	m_box_margin_pos.set_spacing(5);
167 	m_box_margin_pos.pack_start(m_lbl_margin_pos, Gtk::PACK_SHRINK);
168 	m_box_margin_pos.pack_start(m_ent_margin_pos, Gtk::PACK_EXPAND_WIDGET);
169 	m_box_wrap.set_spacing(5);
170 	m_box_wrap.set_border_width(5);
171 	m_box_wrap.pack_start(m_btn_wrap_text, Gtk::PACK_SHRINK);
172 	m_box_wrap.pack_start(m_btn_wrap_words, Gtk::PACK_SHRINK);
173 
174 	m_box_linenum.set_spacing(5);
175 	m_box_linenum.set_border_width(5);
176 	m_box_linenum.pack_start(m_btn_linenum_display, Gtk::PACK_SHRINK);
177 
178 	m_box_curline.set_spacing(5);
179 	m_box_curline.set_border_width(5);
180 	m_box_curline.pack_start(m_btn_curline_highlight, Gtk::PACK_SHRINK);
181 
182 	m_box_margin.set_spacing(5);
183 	m_box_margin.set_border_width(5);
184 	m_box_margin.pack_start(m_btn_margin_display, Gtk::PACK_SHRINK);
185 	m_box_margin.pack_start(m_box_margin_pos, Gtk::PACK_SHRINK);
186 
187 	m_box_bracket.set_spacing(5);
188 	m_box_bracket.set_border_width(5);
189 	m_box_bracket.pack_start(m_btn_bracket_highlight, Gtk::PACK_SHRINK);
190 
191 	m_frame_wrap.add(m_box_wrap);
192 	m_frame_linenum.add(m_box_linenum);
193 	m_frame_curline.add(m_box_curline);
194 	m_frame_margin.add(m_box_margin);
195 	m_frame_bracket.add(m_box_bracket);
196 
197 	m_box.set_spacing(5);
198 	m_box.pack_start(m_frame_wrap, Gtk::PACK_SHRINK);
199 	m_box.pack_start(m_frame_linenum, Gtk::PACK_SHRINK);
200 	m_box.pack_start(m_frame_curline, Gtk::PACK_SHRINK);
201 	m_box.pack_start(m_frame_margin, Gtk::PACK_SHRINK);
202 	m_box.pack_start(m_frame_bracket, Gtk::PACK_SHRINK);
203 
204 	add(m_box);
205 }
206 
set(Preferences::View & view) const207 void Gobby::PreferencesDialog::View::set(Preferences::View& view) const
208 {
209 	view.wrap_text = m_btn_wrap_text.get_active();
210 	view.wrap_words = !m_btn_wrap_words.get_active();
211 	view.linenum_display = m_btn_linenum_display.get_active();
212 	view.curline_highlight = m_btn_curline_highlight.get_active();
213 	view.margin_display = m_btn_margin_display.get_active();
214 	view.margin_pos = m_ent_margin_pos.get_value_as_int();
215 	view.bracket_highlight = m_btn_bracket_highlight.get_active();
216 }
217 
on_margin_display_toggled()218 void Gobby::PreferencesDialog::View::on_margin_display_toggled()
219 {
220 	m_box_margin_pos.set_sensitive(m_btn_margin_display.get_active() );
221 }
222 
223 Gobby::PreferencesDialog::Appearance::
Appearance(const Gobby::Preferences & preferences)224 	Appearance(const Gobby::Preferences& preferences):
225 	m_frame_toolbar(_("Toolbar") ),
226 	m_frame_windows(_("Windows") ),
227 	m_btn_remember(_("Remember the positions and states") ),
228 	m_btn_urgency_hint(
229 	  _("Highlight the window on incoming chat messages") )
230 {
231 	Gtk::ToolbarStyle style = preferences.appearance.toolbar_show;
232 	bool remember = preferences.appearance.remember;
233 	bool urgency_hint = preferences.appearance.urgency_hint;
234 
235 	m_cmb_toolbar_style.append_text(_("Show text only") );
236 	m_cmb_toolbar_style.append_text(_("Show icons only") );
237 	m_cmb_toolbar_style.append_text(_("Show both icons and text") );
238 	m_cmb_toolbar_style.append_text(_("Show text besides icons") );
239 
240 	switch(style)
241 	{
242 	case Gtk::TOOLBAR_TEXT: m_cmb_toolbar_style.set_active(0); break;
243 	case Gtk::TOOLBAR_ICONS: m_cmb_toolbar_style.set_active(1); break;
244 	case Gtk::TOOLBAR_BOTH: m_cmb_toolbar_style.set_active(2); break;
245 	case Gtk::TOOLBAR_BOTH_HORIZ: m_cmb_toolbar_style.set_active(3); break;
246 	default: break; // Avoids compiler warnings
247 	}
248 
249 	m_box_toolbar.set_spacing(5);
250 	m_box_toolbar.set_border_width(5);
251 	m_box_toolbar.pack_start(m_cmb_toolbar_style, Gtk::PACK_SHRINK);
252 
253 	m_frame_toolbar.add(m_box_toolbar);
254 
255 	m_box_windows.set_spacing(5);
256 	m_box_windows.set_border_width(5);
257 	m_btn_remember.set_active(remember);
258 	m_btn_urgency_hint.set_active(urgency_hint);
259 	m_box_windows.pack_start(m_btn_remember, Gtk::PACK_SHRINK);
260 	m_box_windows.pack_start(m_btn_urgency_hint, Gtk::PACK_SHRINK);
261 
262 	m_frame_windows.add(m_box_windows);
263 
264 	m_box.set_spacing(5);
265 	m_box.pack_start(m_frame_toolbar, Gtk::PACK_SHRINK);
266 	m_box.pack_start(m_frame_windows, Gtk::PACK_SHRINK);
267 
268 	add(m_box);
269 }
270 
271 void Gobby::PreferencesDialog::Appearance::
set(Preferences::Appearance & appearance) const272 	set(Preferences::Appearance& appearance) const
273 {
274 	appearance.toolbar_show = rownum_to_toolstyle(
275 		m_cmb_toolbar_style.get_active_row_number()
276 	);
277 
278 	appearance.remember = m_btn_remember.get_active();
279 	appearance.urgency_hint = m_btn_urgency_hint.get_active();
280 }
281 
Font(const Preferences & preferences)282 Gobby::PreferencesDialog::Font::Font(const Preferences& preferences):
283 	m_init_font(preferences.font.desc.to_string() )
284 {
285 	// Call to set_font_name does not work before realization of the
286 	// font selection widget
287 	m_font_sel.signal_realize().connect(
288 		sigc::mem_fun(*this, &Font::on_fontsel_realize)
289 	);
290 
291 	add(m_font_sel);
292 }
293 
on_fontsel_realize()294 void Gobby::PreferencesDialog::Font::on_fontsel_realize()
295 {
296 	m_font_sel.set_font_name(m_init_font);
297 	m_init_font.clear();
298 }
299 
set(Preferences::Font & font) const300 void Gobby::PreferencesDialog::Font::set(Preferences::Font& font) const
301 {
302 	if(m_init_font.empty() )
303 		font.desc = Pango::FontDescription(m_font_sel.get_font_name());
304 	else
305 		font.desc = Pango::FontDescription(m_init_font);
306 }
307 
Behaviour(const Preferences & preferences)308 Gobby::PreferencesDialog::Behaviour::Behaviour(const Preferences& preferences):
309 	m_frame_documents(_("Document management") ),
310 	m_btn_auto_open(_("Open new remotely-created documents automatically") )
311 {
312 	bool auto_open = preferences.behaviour.auto_open_new_documents;
313 
314 	m_btn_auto_open.set_active(auto_open);
315 	m_box_documents.set_spacing(5);
316 	m_box_documents.set_border_width(5);
317 	m_box_documents.pack_start(m_btn_auto_open, Gtk::PACK_SHRINK);
318 	m_frame_documents.add(m_box_documents);
319 
320 	m_box.pack_start(m_frame_documents, Gtk::PACK_SHRINK);
321 
322 	add(m_box);
323 }
324 
set(Preferences::Behaviour & preferences) const325 void Gobby::PreferencesDialog::Behaviour::set(
326 	Preferences::Behaviour& preferences) const
327 {
328 	preferences.auto_open_new_documents = m_btn_auto_open.get_active();
329 }
330 
LanguageColumns()331 Gobby::PreferencesDialog::FileList::LanguageColumns::LanguageColumns()
332 {
333 	add(language);
334 	add(language_name);
335 }
336 
FileColumns()337 Gobby::PreferencesDialog::FileList::FileColumns::FileColumns()
338 {
339 	add(pattern);
340 	add(mime_type);
341 	add(language);
342 }
343 
FileList(Gtk::Window & parent,const Preferences & preferences,GtkSourceLanguageManager * lang_mgr)344 Gobby::PreferencesDialog::FileList::FileList(Gtk::Window& parent,
345                                              const Preferences& preferences,
346                                              GtkSourceLanguageManager* lang_mgr):
347 	m_parent(parent), m_lang_mgr(lang_mgr),
348 	m_viewcol_pattern(_("Pattern"), file_columns.pattern),
349 	m_viewcol_lang(_("Language"), m_renderer_lang),
350 	m_viewcol_mimetype(_("Mime type"), file_columns.mime_type),
351 	m_intro(
352 		_("This is a list of all recognized file types"),
353 		Gtk::ALIGN_LEFT
354 	),
355 	m_hbox(Gtk::BUTTONBOX_END, 12),
356 	m_btn_add(Gtk::Stock::ADD), m_btn_remove(Gtk::Stock::REMOVE),
357 	m_lang_list(Gtk::ListStore::create(lang_columns) ),
358 	m_file_list(Gtk::ListStore::create(file_columns) )
359 {
360 #ifdef WITH_GTKSOURCEVIEW2
361 	GSList* languages = NULL;
362 	const gchar* const* ids = gtk_source_language_manager_get_language_ids(lang_mgr);
363 	if(ids != NULL)
364 	{
365 		for(const gchar* const* id = ids; *id != NULL; ++ id)
366 		{
367 			GtkSourceLanguage* language = gtk_source_language_manager_get_language(lang_mgr, *id);
368 			if(gtk_source_language_get_hidden(language)) continue;
369 
370 			languages = g_slist_prepend(languages, language);
371 		}
372 	}
373 #else
374 	const GSList* list =
375 		gtk_source_languages_manager_get_available_languages(lang_mgr);
376 	GSList* languages = g_slist_copy(const_cast<GSList*>(list));
377 #endif
378 
379 	languages = g_slist_sort(languages, &lang_sort);
380 
381 	for(GSList* iter = languages; iter != NULL; iter = iter->next)
382 	{
383 		Gtk::TreeIter tree_it = m_lang_list->append();
384 		(*tree_it)[lang_columns.language] = GTK_SOURCE_LANGUAGE(iter->data);
385 		(*tree_it)[lang_columns.language_name] =
386 			gtk_source_language_get_name(GTK_SOURCE_LANGUAGE(iter->data));
387 
388 		m_lang_map[GTK_SOURCE_LANGUAGE(iter->data)] = tree_it;
389 	}
390 
391 	g_slist_free(languages);
392 
393 	m_renderer_pattern = static_cast<Gtk::CellRendererText*>(
394 		m_viewcol_pattern.get_first_cell_renderer()
395 	);
396 
397 	m_renderer_mimetype = static_cast<Gtk::CellRendererText*>(
398 		m_viewcol_mimetype.get_first_cell_renderer()
399 	);
400 
401 	m_renderer_pattern->property_editable() = true;
402 	m_renderer_pattern->signal_edited().connect(
403 		sigc::mem_fun(*this, &FileList::on_pattern_edited)
404 	);
405 
406 	m_renderer_mimetype->property_editable() = true;
407 	m_renderer_mimetype->signal_edited().connect(
408 		sigc::mem_fun(*this, &FileList::on_mimetype_edited)
409 	);
410 
411 	m_renderer_lang.property_has_entry() = false;
412 	m_renderer_lang.property_model() = m_lang_list;
413 	m_renderer_lang.property_text_column() = 1;
414 	m_renderer_lang.property_editable() = true;
415 	m_renderer_lang.signal_edited().connect(
416 		sigc::mem_fun(*this, &FileList::on_language_edited)
417 	);
418 
419 	m_viewcol_lang.set_cell_data_func(
420 		m_renderer_lang,
421 		sigc::mem_fun(*this, &FileList::cell_data_file_language)
422 	);
423 
424 
425 	m_viewcol_pattern.set_sort_column(file_columns.pattern);
426 	//m_viewcol_lang.set_sort_column(file_columns.language);
427 	m_viewcol_mimetype.set_sort_column(file_columns.mime_type);
428 
429 	const Preferences::FileList& filelist = preferences.files;
430 
431 	for(Preferences::FileList::iterator iter = filelist.begin();
432 	    iter != filelist.end();
433 	    ++ iter)
434 	{
435 /*		std::list<Glib::ustring> mime_types =
436 			iter.language()->get_mime_types();*/
437 
438 		Gtk::TreeIter tree_it = m_file_list->append();
439 		(*tree_it)[file_columns.pattern] = iter.pattern();
440 		set_language(tree_it, iter.language() );
441 	}
442 
443 	m_view.set_model(m_file_list);
444 
445 	m_view.append_column(m_viewcol_pattern);
446 	m_view.append_column(m_viewcol_lang);
447 	//m_view.append_column(m_viewcol_mimetype);
448 
449 	m_view.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
450 	m_view.get_selection()->signal_changed().connect(
451 		sigc::mem_fun(*this, &FileList::on_selection_changed)
452 	);
453 
454 	m_view.set_rules_hint(true);
455 
456 	m_btn_add.signal_clicked().connect(
457 		sigc::mem_fun(*this, &FileList::on_file_add)
458 	);
459 
460 	m_btn_remove.signal_clicked().connect(
461 		sigc::mem_fun(*this, &FileList::on_file_remove)
462 	);
463 
464 	m_hbox.add(m_btn_remove);
465 	m_hbox.add(m_btn_add);
466 
467 	m_wnd.add(m_view);
468 	m_wnd.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
469 	m_wnd.set_shadow_type(Gtk::SHADOW_IN);
470 
471 	m_vbox.pack_start(m_intro, Gtk::PACK_SHRINK);
472 	m_vbox.pack_start(m_wnd, Gtk::PACK_EXPAND_WIDGET);
473 	m_vbox.pack_start(m_hbox, Gtk::PACK_SHRINK);
474 	m_vbox.set_spacing(8);
475 
476 	add(m_vbox);
477 
478 	on_selection_changed();
479 }
480 
set(Preferences::FileList & files) const481 void Gobby::PreferencesDialog::FileList::set(Preferences::FileList& files) const
482 {
483 	Gtk::TreeNodeChildren children = m_file_list->children();
484 	for(Gtk::TreeIter iter = children.begin();
485 	    iter != children.end();
486 	    ++ iter)
487 	{
488 		Gtk::TreeIter lang_it = (*iter)[file_columns.language];
489 		files.add(
490 			(*iter)[file_columns.pattern],
491 			(*lang_it)[lang_columns.language]
492 		);
493 	}
494 }
495 
496 void Gobby::PreferencesDialog::FileList::
cell_data_file_language(Gtk::CellRenderer * renderer,const Gtk::TreeIter & iter)497 	cell_data_file_language(Gtk::CellRenderer* renderer,
498 	                        const Gtk::TreeIter& iter)
499 {
500 	Gtk::TreeIter lang_it = (*iter)[file_columns.language];
501 	static_cast<Gtk::CellRendererText*>(renderer)->property_text() =
502 		(*lang_it)[lang_columns.language_name];
503 }
504 
505 void Gobby::PreferencesDialog::FileList::
on_pattern_edited(const Glib::ustring & path,const Glib::ustring & new_text)506 	on_pattern_edited(const Glib::ustring& path,
507 	                  const Glib::ustring& new_text)
508 {
509 	if(new_text.empty() )
510 	{
511 		Gtk::MessageDialog dlg(
512 			m_parent,
513 			_("Pattern must not be empty."),
514 			false,
515 			Gtk::MESSAGE_ERROR,
516 			Gtk::BUTTONS_OK,
517 			true
518 		);
519 
520 		dlg.run();
521 
522 		// TODO: Take iterator at beginning and remove here back to
523 		// path to avoid borkage
524 		/*m_view.set_cursor(
525 			Gtk::TreePath(path),
526 			m_viewcol_pattern,
527 			true
528 		);*/
529 	}
530 	else
531 	{
532 		Gtk::TreeIter iter = m_file_list->get_iter(Gtk::TreePath(path));
533 		(*iter)[file_columns.pattern] = new_text;
534 	}
535 }
536 
537 void Gobby::PreferencesDialog::FileList::
on_mimetype_edited(const Glib::ustring & path,const Glib::ustring & new_text)538 	on_mimetype_edited(const Glib::ustring& path,
539 	                   const Glib::ustring& new_text)
540 {
541 #ifdef WITH_GTKSOURCEVIEW2
542 	const gchar* const* ids = gtk_source_language_manager_get_language_ids(m_lang_mgr);
543 
544 	GtkSourceLanguage* lang = NULL;
545 
546 	if(ids != NULL)
547 	{
548 		for(const gchar* const* id = ids; *id != NULL; ++ id)
549 		{
550 			GtkSourceLanguage* language = gtk_source_language_manager_get_language(m_lang_mgr, *id);
551 			if(gtk_source_language_get_hidden(language)) continue;
552 
553 			gchar** mime_types = gtk_source_language_get_mime_types(language);
554 			for(gchar** mime_type = mime_types; *mime_type != NULL; ++ mime_type)
555 			{
556 				if(strcmp(*mime_type, new_text.c_str()) == 0)
557 				{
558 					lang = language;
559 					break;
560 				}
561 			}
562 
563 			g_strfreev(mime_types);
564 			if(lang != NULL) break;
565 		}
566 	}
567 #else
568 	GtkSourceLanguage* lang =
569 		gtk_source_languages_manager_get_language_from_mime_type(
570 			m_lang_mgr, new_text.c_str());
571 #endif
572 
573 	if(!lang)
574 	{
575 		obby::format_string str(
576 			_("There is no language with the mime type '%0%'.")
577 		);
578 
579 		str << new_text.raw();
580 
581 		Gtk::MessageDialog dlg(
582 			m_parent,
583 			str.str(),
584 			false,
585 			Gtk::MESSAGE_ERROR,
586 			Gtk::BUTTONS_OK,
587 			true
588 		);
589 
590 		dlg.run();
591 	}
592 	else
593 	{
594 		set_language(m_file_list->get_iter(Gtk::TreePath(path)), lang);
595 	}
596 }
597 
598 void Gobby::PreferencesDialog::FileList::
on_language_edited(const Glib::ustring & path,const Glib::ustring & new_text)599 	on_language_edited(const Glib::ustring& path,
600 	                   const Glib::ustring& new_text)
601 {
602 	// We do not get an iterator/path/whatever that points to the
603 	// chosen language in the language list.
604 	GtkSourceLanguage* lang = NULL;
605 	Gtk::TreeNodeChildren children = m_lang_list->children();
606 
607 	for(Gtk::TreeIter iter = children.begin();
608 	    iter != children.end();
609 	    ++ iter)
610 	{
611 		if( (*iter)[lang_columns.language_name] == new_text)
612 		{
613 			lang = (*iter)[lang_columns.language];
614 			break;
615 		}
616 	}
617 
618 	if(!lang)
619 	{
620 		// The language must exist since we added all available
621 		// languages to that list
622 		throw std::logic_error(
623 			"Gobby::PreferencesDialog::FileList::"
624 			"on_language_edited:\n"
625 			"Chosen language is not in language list"
626 		);
627 	}
628 
629 	set_language(m_file_list->get_iter(Gtk::TreePath(path)), lang);
630 }
631 
on_selection_changed()632 void Gobby::PreferencesDialog::FileList::on_selection_changed()
633 {
634 	std::list<Gtk::TreePath> list =
635 		m_view.get_selection()->get_selected_rows();
636 
637 	m_btn_remove.set_sensitive(list.begin() != list.end() );
638 }
639 
on_file_add()640 void Gobby::PreferencesDialog::FileList::on_file_add()
641 {
642 	Gtk::TreeIter iter = m_file_list->append();
643 	set_language(iter, m_lang_map.begin()->first);
644 
645 	m_view.set_cursor(
646 		m_file_list->get_path(iter),
647 		m_viewcol_pattern,
648 		true
649 	);
650 }
651 
on_file_remove()652 void Gobby::PreferencesDialog::FileList::on_file_remove()
653 {
654 	std::list<Gtk::TreePath> list =
655 		m_view.get_selection()->get_selected_rows();
656 
657 	std::list<Gtk::TreeIter> iter_list;
658 
659 	// Path offsets get borked when removing multiple rows, so we
660 	// convert all paths to iterators before
661 	for(std::list<Gtk::TreePath>::const_iterator iter = list.begin();
662 	    iter != list.end();
663 	    ++ iter)
664 	{
665 		iter_list.push_back(m_file_list->get_iter(*iter) );
666 	}
667 
668 	for(std::list<Gtk::TreeIter>::const_iterator iter = iter_list.begin();
669 	    iter != iter_list.end();
670 	    ++ iter)
671 	{
672 		m_file_list->erase(*iter);
673 	}
674 }
675 
set_language(const Gtk::TreeIter & row,GtkSourceLanguage * lang)676 void Gobby::PreferencesDialog::FileList::set_language(const Gtk::TreeIter& row,
677                                                       GtkSourceLanguage* lang)
678 {
679 	map_type::const_iterator lang_it = m_lang_map.find(lang);
680 	if(lang_it == m_lang_map.end() )
681 	{
682 		throw std::logic_error(
683 			"Gobby::PreferencesDialog::FileList::set_language:\n"
684 			"Given language is not in language map"
685 		);
686 	}
687 
688 	(*row)[file_columns.language] = lang_it->second;
689 #ifdef WITH_GTKSOURCEVIEW2
690 	gchar** mime_types = gtk_source_language_get_mime_types(lang);
691 
692 	if(mime_types && *mime_types)
693 		(*row)[file_columns.mime_type] = *mime_types;
694 
695 	g_strfreev(mime_types);
696 #else
697 	GSList* mime_types = gtk_source_language_get_mime_types(lang);
698 	for(GSList* cur = mime_types; cur != NULL; cur = cur->next)
699 	{
700 		if(cur == mime_types)
701 			(*row)[file_columns.mime_type] =
702 				static_cast<gchar*>(cur->data);
703 		g_free(cur->data);
704 	}
705 	g_slist_free(mime_types);
706 #endif
707 }
708 
PreferencesDialog(Gtk::Window & parent,const Preferences & preferences,GtkSourceLanguageManager * lang_mgr,bool local)709 Gobby::PreferencesDialog::PreferencesDialog(Gtk::Window& parent,
710                                             const Preferences& preferences,
711                                             GtkSourceLanguageManager* lang_mgr,
712                                             bool local)
713  : Gtk::Dialog(_("Preferences"), parent, true),
714 #ifndef GTKMM_DISABLE_DEPRECATED
715    m_page_editor(preferences, m_tooltips),
716 #else
717    m_page_editor(preferences),
718 #endif
719    m_page_view(preferences),
720    m_page_appearance(preferences),
721    m_page_font(preferences),
722    m_page_behaviour(preferences),
723    m_page_files(*this, preferences, lang_mgr)
724 {
725 	m_notebook.append_page(m_page_editor, _("Editor") );
726 	m_notebook.append_page(m_page_view, _("View") );
727 
728 	// Appearance only affects the global Gobby window
729 	if(!local) m_notebook.append_page(m_page_appearance, _("Appearance") );
730 	m_notebook.append_page(m_page_font, _("Font") );
731 	if(!local) m_notebook.append_page(m_page_behaviour, _("Behaviour") );
732 	if(!local) m_notebook.append_page(m_page_files, _("Files") );
733 
734 	get_vbox()->set_spacing(5);
735 	get_vbox()->pack_start(m_notebook, Gtk::PACK_EXPAND_WIDGET);
736 
737 	add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
738 	add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
739 
740 	set_border_width(10);
741 	set_default_size(350, 400);
742 	//set_resizable(false);
743 
744 	show_all();
745 }
746 
set(Preferences & preferences) const747 void Gobby::PreferencesDialog::set(Preferences& preferences) const
748 {
749 	m_page_editor.set(preferences.editor);
750 	m_page_view.set(preferences.view);
751 	m_page_appearance.set(preferences.appearance);
752 	m_page_font.set(preferences.font);
753 	m_page_behaviour.set(preferences.behaviour);
754 	m_page_files.set(preferences.files);
755 }
756 
757