1 /* $Id$ */
2 static char const _copyright[] =
3 "Copyright © 2006-2016 Pierre Pronchery <khorben@defora.org>";
4 /* This file is part of DeforaOS Desktop Editor */
5 static char const _license[] =
6 "All rights reserved.\n"
7 "\n"
8 "Redistribution and use in source and binary forms, with or without\n"
9 "modification, are permitted provided that the following conditions are\n"
10 "met:\n"
11 "\n"
12 "1. Redistributions of source code must retain the above copyright\n"
13 "   notice, this list of conditions and the following disclaimer.\n"
14 "\n"
15 "2. Redistributions in binary form must reproduce the above copyright\n"
16 "   notice, this list of conditions and the following disclaimer in the\n"
17 "   documentation and/or other materials provided with the distribution.\n"
18 "\n"
19 "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n"
20 "IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n"
21 "TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n"
22 "PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n"
23 "HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n"
24 "SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n"
25 "TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n"
26 "PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n"
27 "LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n"
28 "NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n"
29 "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";
30 /* TODO:
31  * - add a "Back" button to the "Find" dialog
32  * - add a "Replace" dialog
33  * - consider using GtkSourceView also/instead */
34 
35 
36 
37 #include <sys/stat.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <limits.h>
41 #include <errno.h>
42 #include <math.h>
43 #include <libintl.h>
44 #include <gdk/gdkkeysyms.h>
45 #include <Desktop.h>
46 #include "callbacks.h"
47 #include "editor.h"
48 #include "../config.h"
49 #define _(string) gettext(string)
50 #define N_(string) (string)
51 
52 
53 /* Editor */
54 /* private */
55 /* constants */
56 #ifndef PROGNAME
57 # define PROGNAME		"editor"
58 #endif
59 #define EDITOR_CONFIG_FILE	".editor"
60 #define EDITOR_DEFAULT_FONT	"Monospace 9"
61 
62 
63 /* types */
64 struct _Editor
65 {
66 	EditorPrefs prefs;
67 	gchar * filename;
68 	size_t search;
69 
70 	Config * config;
71 
72 	/* widgets */
73 	GtkWidget * window;
74 	PangoFontDescription * bold;
75 	GtkWidget * view;
76 	GtkWidget * statusbar;
77 	guint statusbar_id;
78 #if GTK_CHECK_VERSION(2, 18, 0)
79 	/* infobar */
80 	GtkWidget * infobar;
81 	GtkWidget * infobar_label;
82 #endif
83 	/* preferences */
84 	GtkWidget * pr_window;
85 	GtkWidget * pr_font;
86 	GtkWidget * pr_wrap;
87 	/* find */
88 	GtkWidget * fi_dialog;
89 	GtkListStore * fi_store;
90 	GtkWidget * fi_text;
91 	GtkWidget * fi_entry;
92 	GtkWidget * fi_case;
93 	GtkWidget * fi_wrap;
94 	/* about */
95 	GtkWidget * ab_window;
96 
97 	/* printing */
98 	PangoFontDescription * font;
99 	double font_size;
100 	double line_space;
101 	GtkTextIter iter;
102 	guint line_count;
103 };
104 
105 
106 /* variables */
107 static char const * _authors[] =
108 {
109 	"Pierre Pronchery <khorben@defora.org>",
110 	NULL
111 };
112 
113 #ifdef EMBEDDED
114 static const DesktopAccel _editor_accel[] =
115 {
116 	{ G_CALLBACK(on_close), GDK_CONTROL_MASK, GDK_KEY_W },
117 	{ G_CALLBACK(on_new), GDK_CONTROL_MASK, GDK_KEY_N },
118 	{ G_CALLBACK(on_open), GDK_CONTROL_MASK, GDK_KEY_O },
119 	{ G_CALLBACK(on_save), GDK_CONTROL_MASK, GDK_KEY_S },
120 	{ G_CALLBACK(on_save_as), GDK_CONTROL_MASK | GDK_SHIFT_MASK,
121 		GDK_KEY_S },
122 	{ NULL, 0, 0 }
123 };
124 #endif
125 
126 #ifndef EMBEDDED
127 static const DesktopMenu _editor_menu_file[] =
128 {
129 	{ N_("_New"), G_CALLBACK(on_file_new), GTK_STOCK_NEW, GDK_CONTROL_MASK,
130 		GDK_KEY_N },
131 	{ N_("_Open"), G_CALLBACK(on_file_open), GTK_STOCK_OPEN,
132 		GDK_CONTROL_MASK, GDK_KEY_O },
133 	{ "", NULL, NULL, 0, 0 },
134 	{ N_("_Save"), G_CALLBACK(on_file_save), GTK_STOCK_SAVE,
135 		GDK_CONTROL_MASK, GDK_KEY_S },
136 	{ N_("Save _As..."), G_CALLBACK(on_file_save_as), GTK_STOCK_SAVE_AS,
137 		GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_KEY_S },
138 	{ "", NULL, NULL, 0, 0 },
139 	{ N_("_Print"), G_CALLBACK(on_file_print),
140 		GTK_STOCK_PRINT, GDK_CONTROL_MASK, GDK_KEY_P },
141 	{ "", NULL, NULL, 0, 0 },
142 	{ N_("Pr_operties"), G_CALLBACK(on_file_properties),
143 		GTK_STOCK_PROPERTIES, GDK_MOD1_MASK, GDK_KEY_Return },
144 	{ "", NULL, NULL, 0, 0 },
145 	{ N_("_Close"), G_CALLBACK(on_file_close), GTK_STOCK_CLOSE, 0, 0 },
146 	{ NULL, NULL, NULL, 0, 0 }
147 };
148 
149 static const DesktopMenu _editor_menu_file_filter[] =
150 {
151 	{ N_("_Save"), G_CALLBACK(on_file_save), GTK_STOCK_SAVE,
152 		GDK_CONTROL_MASK, GDK_KEY_S },
153 	{ "", NULL, NULL, 0, 0 },
154 	{ N_("_Print"), G_CALLBACK(on_file_print),
155 		GTK_STOCK_PRINT, GDK_CONTROL_MASK, GDK_KEY_P },
156 	{ "", NULL, NULL, 0, 0 },
157 	{ N_("Pr_operties"), G_CALLBACK(on_file_properties),
158 		GTK_STOCK_PROPERTIES, GDK_MOD1_MASK, GDK_KEY_Return },
159 	{ "", NULL, NULL, 0, 0 },
160 	{ N_("_Close"), G_CALLBACK(on_file_close), GTK_STOCK_CLOSE, 0, 0 },
161 	{ NULL, NULL, NULL, 0, 0 }
162 };
163 
164 static const DesktopMenu _editor_menu_edit[] =
165 {
166 	{ N_("Cu_t"), G_CALLBACK(on_edit_cut), GTK_STOCK_CUT, GDK_CONTROL_MASK,
167 		GDK_KEY_X },
168 	{ N_("_Copy"), G_CALLBACK(on_edit_copy), GTK_STOCK_COPY,
169 		GDK_CONTROL_MASK, GDK_KEY_C },
170 	{ N_("_Paste"), G_CALLBACK(on_edit_paste), GTK_STOCK_PASTE,
171 		GDK_CONTROL_MASK, GDK_KEY_V },
172 	{ "", NULL, NULL, 0, 0 },
173 	{ N_("Select _All"), G_CALLBACK(on_edit_select_all),
174 #if GTK_CHECK_VERSION(2, 10, 0)
175 		GTK_STOCK_SELECT_ALL,
176 #else
177 		"edit-select-all",
178 #endif
179 		GDK_CONTROL_MASK, GDK_KEY_A },
180 	{ N_("_Unselect all"), G_CALLBACK(on_edit_unselect_all), NULL, 0, 0 },
181 	{ "", NULL, NULL, 0, 0 },
182 	{ N_("_Find"), G_CALLBACK(on_edit_find), GTK_STOCK_FIND,
183 		GDK_CONTROL_MASK, GDK_KEY_F },
184 	{ "", NULL, NULL, 0, 0 },
185 	{ N_("_Preferences"), G_CALLBACK(on_edit_preferences),
186 		GTK_STOCK_PREFERENCES, 0, 0 },
187 	{ NULL, NULL, NULL, 0, 0 }
188 };
189 
190 static const DesktopMenu _editor_menu_insert[] =
191 {
192 	{ N_("_File..."), G_CALLBACK(on_insert_file), "insert-text", 0, 0 },
193 	{ NULL, NULL, NULL, 0, 0 }
194 };
195 
196 static const DesktopMenu _editor_menu_help[] =
197 {
198 	{ N_("_Contents"), G_CALLBACK(on_help_contents), "help-contents",
199 		0, GDK_KEY_F1 },
200 	{ N_("_About"), G_CALLBACK(on_help_about),
201 #if GTK_CHECK_VERSION(2, 6, 0)
202 		GTK_STOCK_ABOUT, 0, 0 },
203 #else
204 		NULL, 0, 0 },
205 #endif
206 	{ NULL, NULL, NULL, 0, 0 }
207 };
208 
209 static const DesktopMenubar _editor_menubar[] =
210 {
211 	{ N_("_File"), _editor_menu_file },
212 	{ N_("_Edit"), _editor_menu_edit },
213 	{ N_("_Insert"), _editor_menu_insert },
214 	{ N_("_Help"), _editor_menu_help },
215 	{ NULL, NULL }
216 };
217 
218 static const DesktopMenubar _editor_menubar_filter[] =
219 {
220 	{ N_("_File"), _editor_menu_file_filter },
221 	{ N_("_Edit"), _editor_menu_edit },
222 	{ N_("_Insert"), _editor_menu_insert },
223 	{ N_("_Help"), _editor_menu_help },
224 	{ NULL, NULL }
225 };
226 #endif
227 
228 static DesktopToolbar _editor_toolbar[] =
229 {
230 	{ N_("New"), G_CALLBACK(on_new), GTK_STOCK_NEW, 0, 0, NULL },
231 	{ N_("Open"), G_CALLBACK(on_open), GTK_STOCK_OPEN, 0, 0, NULL },
232 	{ "", NULL, NULL, 0, 0, NULL },
233 	{ N_("Save"), G_CALLBACK(on_save), GTK_STOCK_SAVE, 0, 0, NULL },
234 	{ N_("Save as"), G_CALLBACK(on_save_as), GTK_STOCK_SAVE_AS, 0, 0,
235 		NULL },
236 	{ "", NULL, NULL, 0, 0, NULL },
237 	{ N_("Cut"), G_CALLBACK(on_cut), GTK_STOCK_CUT, 0, 0, NULL },
238 	{ N_("Copy"), G_CALLBACK(on_copy), GTK_STOCK_COPY, 0, 0, NULL },
239 	{ N_("Paste"), G_CALLBACK(on_paste), GTK_STOCK_PASTE, 0, 0, NULL },
240 #ifdef EMBEDDED
241 	{ "", NULL, NULL, 0, 0, NULL },
242 	{ N_("Print"), G_CALLBACK(on_print), GTK_STOCK_PREFERENCES,
243 		GDK_CONTROL_MASK, GDK_KEY_P, NULL },
244 	{ "", NULL, NULL, 0, 0, NULL },
245 	{ N_("Find"), G_CALLBACK(on_find), GTK_STOCK_FIND, GDK_CONTROL_MASK,
246 		GDK_KEY_F, NULL },
247 	{ "", NULL, NULL, 0, 0, NULL },
248 	{ N_("Preferences"), G_CALLBACK(on_preferences), GTK_STOCK_PREFERENCES,
249 		0, 0, NULL },
250 	{ N_("Properties"), G_CALLBACK(on_properties), GTK_STOCK_PROPERTIES,
251 		GDK_MOD1_MASK, GDK_KEY_Return, NULL },
252 	{ "", NULL, NULL, 0, 0, NULL },
253 	{ N_("Help"), G_CALLBACK(on_help_contents), "help-contents",
254 		0, GDK_KEY_F1, NULL },
255 #endif
256 	{ NULL, NULL, NULL, 0, 0, NULL }
257 };
258 
259 static DesktopToolbar _editor_toolbar_filter[] =
260 {
261 	{ N_("Save"), G_CALLBACK(on_save), GTK_STOCK_SAVE, 0, 0, NULL },
262 	{ "", NULL, NULL, 0, 0, NULL },
263 	{ N_("Cut"), G_CALLBACK(on_cut), GTK_STOCK_CUT, 0, 0, NULL },
264 	{ N_("Copy"), G_CALLBACK(on_copy), GTK_STOCK_COPY, 0, 0, NULL },
265 	{ N_("Paste"), G_CALLBACK(on_paste), GTK_STOCK_PASTE, 0, 0, NULL },
266 #ifdef EMBEDDED
267 	{ "", NULL, NULL, 0, 0, NULL },
268 	{ N_("Find"), G_CALLBACK(on_find), GTK_STOCK_FIND, GDK_CONTROL_MASK,
269 		GDK_KEY_F, NULL },
270 	{ "", NULL, NULL, 0, 0, NULL },
271 	{ N_("Preferences"), G_CALLBACK(on_preferences), GTK_STOCK_PREFERENCES,
272 		0, 0, NULL },
273 	{ N_("Properties"), G_CALLBACK(on_properties), GTK_STOCK_PROPERTIES,
274 		GDK_MOD1_MASK, GDK_KEY_Return, NULL },
275 	{ "", NULL, NULL, 0, 0, NULL },
276 	{ N_("Help"), G_CALLBACK(on_help_contents), "help-contents",
277 		0, GDK_KEY_F1, NULL },
278 #endif
279 	{ NULL, NULL, NULL, 0, 0, NULL }
280 };
281 
282 static struct
283 {
284 	char const * name;
285 	GtkWrapMode wrap;
286 } _editor_wrap[] =
287 {
288 	{ N_("none"),			GTK_WRAP_NONE		},
289 	{ N_("characters"),		GTK_WRAP_CHAR		},
290 	{ N_("words"),			GTK_WRAP_WORD		},
291 	{ N_("words then characters"),	GTK_WRAP_WORD_CHAR	}
292 };
293 
294 
295 /* prototypes */
296 static int _editor_set_filename(Editor * editor, char const * filename);
297 static void _editor_set_status(Editor * editor, char const * status);
298 
299 static char * _editor_config_filename(void);
300 static gboolean _editor_find(Editor * editor, char const * text,
301 		gboolean sensitive, gboolean wrap);
302 static void _editor_refresh_title(Editor * editor);
303 
304 /* callbacks */
305 #if GTK_CHECK_VERSION(2, 16, 0)
306 static void _editor_on_find_clear(gpointer data);
307 #endif
308 static void _editor_on_find_clicked(gpointer data);
309 static void _editor_on_find_hide(gpointer data);
310 static void _editor_on_modified(GtkTextBuffer * tbuf, gpointer data);
311 
312 /* helpers */
313 static void _helper_file_dialog_filters(GtkWidget * dialog);
314 
315 
316 /* public */
317 /* functions */
318 /* editor_new */
editor_new(EditorPrefs * prefs)319 Editor * editor_new(EditorPrefs * prefs)
320 {
321 	Editor * editor;
322 	GtkAccelGroup * group;
323 	GtkWidget * vbox;
324 	GtkWidget * hbox;
325 	GtkWidget * widget;
326 	GtkTextBuffer * tbuf;
327 
328 	if((editor = object_new(sizeof(*editor))) == NULL)
329 		return NULL;
330 	if(prefs != NULL)
331 		editor->prefs = *prefs;
332 	else
333 		memset(&editor->prefs, 0, sizeof(editor->prefs));
334 	editor->config = config_new();
335 	editor->window = NULL;
336 	if(editor->config == NULL)
337 	{
338 		editor_delete(editor);
339 		return NULL;
340 	}
341 	editor->filename = NULL;
342 	editor->search = 0;
343 	editor_config_load(editor);
344 	/* widgets */
345 	group = gtk_accel_group_new();
346 	editor->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
347 	gtk_window_add_accel_group(GTK_WINDOW(editor->window), group);
348 	g_object_unref(group);
349 	gtk_window_set_default_size(GTK_WINDOW(editor->window), 600, 400);
350 	_editor_refresh_title(editor);
351 #if GTK_CHECK_VERSION(2, 6, 0)
352 	gtk_window_set_icon_name(GTK_WINDOW(editor->window), "text-editor");
353 #endif
354 	g_signal_connect_swapped(G_OBJECT(editor->window), "delete-event",
355 			G_CALLBACK(on_closex), editor);
356 	editor->bold = pango_font_description_new();
357 	pango_font_description_set_weight(editor->bold, PANGO_WEIGHT_BOLD);
358 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
359 	/* menubar */
360 #ifndef EMBEDDED
361 	widget = desktop_menubar_create(editor->prefs.filter
362 			? _editor_menubar_filter : _editor_menubar, editor,
363 			group);
364 	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
365 #else
366 	desktop_accel_create(_editor_accel, editor, group);
367 #endif
368 	/* toolbar */
369 	widget = desktop_toolbar_create(editor->prefs.filter
370 			? _editor_toolbar_filter : _editor_toolbar, editor,
371 			group);
372 	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);
373 #if GTK_CHECK_VERSION(2, 18, 0)
374 	/* infobar */
375 	editor->infobar = gtk_info_bar_new_with_buttons(GTK_STOCK_CLOSE,
376 			GTK_RESPONSE_CLOSE, NULL);
377 	gtk_info_bar_set_message_type(GTK_INFO_BAR(editor->infobar),
378 			GTK_MESSAGE_ERROR);
379 	g_signal_connect(editor->infobar, "close", G_CALLBACK(gtk_widget_hide),
380 			NULL);
381 	g_signal_connect(editor->infobar, "response", G_CALLBACK(
382 				gtk_widget_hide), NULL);
383 	widget = gtk_info_bar_get_content_area(GTK_INFO_BAR(editor->infobar));
384 	editor->infobar_label = gtk_label_new(NULL);
385 	gtk_widget_show(editor->infobar_label);
386 	gtk_box_pack_start(GTK_BOX(widget), editor->infobar_label, TRUE, TRUE,
387 			0);
388 	gtk_widget_set_no_show_all(editor->infobar, TRUE);
389 	gtk_box_pack_start(GTK_BOX(vbox), editor->infobar, FALSE, TRUE, 0);
390 #endif
391 	/* view */
392 	widget = gtk_scrolled_window_new(NULL, NULL);
393 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget),
394 			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
395 	editor->view = gtk_text_view_new();
396 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
397 	g_signal_connect(tbuf, "modified-changed", G_CALLBACK(
398 				_editor_on_modified), editor);
399 	editor_set_font(editor, editor_get_font(editor));
400 	editor_set_wrap_mode(editor, editor_get_wrap_mode(editor));
401 	gtk_container_add(GTK_CONTAINER(widget), editor->view);
402 	gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 0);
403 	/* find */
404 	editor->fi_dialog = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
405 	hbox = editor->fi_dialog;
406 	gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
407 	widget = gtk_label_new(_("Find:"));
408 	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
409 	editor->fi_store = gtk_list_store_new(1, G_TYPE_STRING);
410 #if GTK_CHECK_VERSION(2, 24, 0)
411 	editor->fi_text = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(
412 				editor->fi_store));
413 	gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(editor->fi_text), 0);
414 #else
415 	editor->fi_text = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(
416 				editor->fi_store), 0);
417 #endif
418 	editor->fi_entry = gtk_bin_get_child(GTK_BIN(editor->fi_text));
419 	g_signal_connect_swapped(editor->fi_entry, "activate", G_CALLBACK(
420 				_editor_on_find_clicked), editor);
421 #if GTK_CHECK_VERSION(2, 16, 0)
422 	gtk_entry_set_icon_from_stock(GTK_ENTRY(editor->fi_entry),
423 			GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR);
424 	g_signal_connect_swapped(editor->fi_entry, "icon-release", G_CALLBACK(
425 				_editor_on_find_clear), editor);
426 #endif
427 	gtk_box_pack_start(GTK_BOX(hbox), editor->fi_text, FALSE, TRUE, 0);
428 	editor->fi_case = gtk_check_button_new_with_label(_("Case-sensitive"));
429 	gtk_box_pack_start(GTK_BOX(hbox), editor->fi_case, FALSE, TRUE, 0);
430 	editor->fi_wrap = gtk_check_button_new_with_label(_("Wrap"));
431 	gtk_box_pack_start(GTK_BOX(hbox), editor->fi_wrap, FALSE, TRUE, 0);
432 	widget = gtk_button_new_from_stock(GTK_STOCK_FIND);
433 	g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
434 				_editor_on_find_clicked), editor);
435 	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
436 	widget = gtk_button_new();
437 	gtk_button_set_image(GTK_BUTTON(widget), gtk_image_new_from_stock(
438 				GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON));
439 	gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE);
440 	g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
441 				_editor_on_find_hide), editor);
442 	gtk_box_pack_end(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
443 	gtk_widget_show_all(hbox);
444 	gtk_widget_hide(hbox);
445 	gtk_widget_set_no_show_all(hbox, TRUE);
446 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
447 	/* statusbar */
448 	editor->statusbar = gtk_statusbar_new();
449 	editor->statusbar_id = 0;
450 	_editor_set_status(editor, _("Ready"));
451 	gtk_box_pack_end(GTK_BOX(vbox), editor->statusbar, FALSE, TRUE, 0);
452 	/* preferences */
453 	editor->pr_window = NULL;
454 	/* about */
455 	editor->ab_window = NULL;
456 	gtk_container_add(GTK_CONTAINER(editor->window), vbox);
457 	gtk_window_set_focus(GTK_WINDOW(editor->window), editor->view);
458 	gtk_widget_show_all(editor->window);
459 	return editor;
460 }
461 
462 
463 /* editor_delete */
editor_delete(Editor * editor)464 void editor_delete(Editor * editor)
465 {
466 #ifdef DEBUG
467 	fprintf(stderr, "DEBUG: %s()\n", __func__);
468 #endif
469 	if(editor->window != NULL)
470 		gtk_widget_destroy(editor->window);
471 	if(editor->config != NULL)
472 		config_delete(editor->config);
473 	pango_font_description_free(editor->bold);
474 	object_delete(editor);
475 }
476 
477 
478 /* accessors */
479 /* editor_get_font */
editor_get_font(Editor * editor)480 char const * editor_get_font(Editor * editor)
481 {
482 	char const * p;
483 	char * q;
484 	GtkSettings * settings;
485 	PangoFontDescription * desc;
486 
487 	if((p = config_get(editor->config, NULL, "font")) != NULL)
488 		return p;
489 	settings = gtk_settings_get_default();
490 	g_object_get(G_OBJECT(settings), "gtk-font-name", &q, NULL);
491 	if(q != NULL)
492 	{
493 		desc = pango_font_description_from_string(q);
494 		g_free(q);
495 		pango_font_description_set_family(desc, "monospace");
496 		q = pango_font_description_to_string(desc);
497 		config_set(editor->config, NULL, "font", q);
498 		g_free(q);
499 		pango_font_description_free(desc);
500 		if((p = config_get(editor->config, NULL, "font")) != NULL)
501 			return p;
502 	}
503 	return EDITOR_DEFAULT_FONT;
504 }
505 
506 
507 /* editor_get_wrap_mode */
editor_get_wrap_mode(Editor * editor)508 GtkWrapMode editor_get_wrap_mode(Editor * editor)
509 {
510 	char const * p;
511 	unsigned int i;
512 
513 	if((p = config_get(editor->config, NULL, "wrap")) != NULL)
514 	{
515 		i = strtoul(p, NULL, 10);
516 		if(i < sizeof(_editor_wrap) / sizeof(*_editor_wrap))
517 			return _editor_wrap[i].wrap;
518 	}
519 	return GTK_WRAP_WORD_CHAR;
520 }
521 
522 
523 /* editor_set_font */
editor_set_font(Editor * editor,char const * font)524 void editor_set_font(Editor * editor, char const * font)
525 {
526 	PangoFontDescription * desc;
527 
528 	desc = pango_font_description_from_string(font);
529 	gtk_widget_override_font(editor->view, desc);
530 	pango_font_description_free(desc);
531 	config_set(editor->config, NULL, "font", font);
532 }
533 
534 
535 /* editor_set_wrap_mode */
editor_set_wrap_mode(Editor * editor,GtkWrapMode wrap)536 void editor_set_wrap_mode(Editor * editor, GtkWrapMode wrap)
537 {
538 	unsigned int i;
539 	char buf[10];
540 
541 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(editor->view),
542 			wrap);
543 	for(i = 0; i < sizeof(_editor_wrap) / sizeof(*_editor_wrap); i++)
544 		if(_editor_wrap[i].wrap == wrap)
545 		{
546 			snprintf(buf, sizeof(buf), "%u", i);
547 			config_set(editor->config, NULL, "wrap", buf);
548 			return;
549 		}
550 }
551 
552 
553 /* useful */
554 /* editor_about */
555 static gboolean _about_on_closex(gpointer data);
556 
editor_about(Editor * editor)557 void editor_about(Editor * editor)
558 {
559 	if(editor->ab_window != NULL)
560 	{
561 		gtk_widget_show(editor->ab_window);
562 		return;
563 	}
564 	editor->ab_window = desktop_about_dialog_new();
565 	gtk_window_set_transient_for(GTK_WINDOW(editor->ab_window),
566 			GTK_WINDOW(editor->window));
567 	desktop_about_dialog_set_authors(editor->ab_window, _authors);
568 	desktop_about_dialog_set_comments(editor->ab_window,
569 			_("Text editor for the DeforaOS desktop"));
570 	desktop_about_dialog_set_copyright(editor->ab_window, _copyright);
571 	desktop_about_dialog_set_logo_icon_name(editor->ab_window,
572 			"text-editor");
573 	desktop_about_dialog_set_license(editor->ab_window, _license);
574 	desktop_about_dialog_set_name(editor->ab_window, PACKAGE);
575 	desktop_about_dialog_set_translator_credits(editor->ab_window,
576 			_("translator-credits"));
577 	desktop_about_dialog_set_version(editor->ab_window, VERSION);
578 	desktop_about_dialog_set_website(editor->ab_window,
579 			"http://www.defora.org/");
580 	g_signal_connect_swapped(G_OBJECT(editor->ab_window), "delete-event",
581 			G_CALLBACK(_about_on_closex), editor);
582 	gtk_widget_show(editor->ab_window);
583 }
584 
_about_on_closex(gpointer data)585 static gboolean _about_on_closex(gpointer data)
586 {
587 	Editor * editor = data;
588 
589 	gtk_widget_hide(editor->ab_window);
590 	return TRUE;
591 }
592 
593 
594 /* editor_config_load */
editor_config_load(Editor * editor)595 int editor_config_load(Editor * editor)
596 {
597 	int ret;
598 	char * filename;
599 
600 	if((filename = _editor_config_filename()) == NULL)
601 		return -1;
602 	ret = config_load(editor->config, filename);
603 	free(filename);
604 	return ret;
605 }
606 
607 
608 /* editor_config_save */
editor_config_save(Editor * editor)609 int editor_config_save(Editor * editor)
610 {
611 	int ret;
612 	char * filename;
613 
614 	if((filename = _editor_config_filename()) == NULL)
615 		return -1;
616 	if((ret = config_save(editor->config, filename)) != 0)
617 		editor_error(editor, _("Could not save the configuration"), 1);
618 	free(filename);
619 	return ret;
620 }
621 
622 
623 /* editor_confirm */
editor_confirm(Editor * editor,char const * message,...)624 int editor_confirm(Editor * editor, char const * message, ...)
625 {
626 	GtkWidget * dialog;
627 	va_list ap;
628 	char const * action;
629 	int res;
630 
631 	dialog = gtk_message_dialog_new(GTK_WINDOW(editor->window),
632 			GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION,
633 			GTK_BUTTONS_NONE,
634 # if GTK_CHECK_VERSION(2, 6, 0)
635 			"%s", _("Question"));
636 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
637 # endif
638 			"%s", message);
639 	va_start(ap, message);
640 	while((action = va_arg(ap, char const *)) != NULL)
641 		gtk_dialog_add_button(GTK_DIALOG(dialog),
642 				action, va_arg(ap, int));
643 	va_end(ap);
644 	gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
645 	res = gtk_dialog_run(GTK_DIALOG(dialog));
646 	gtk_widget_destroy(dialog);
647 	return res;
648 }
649 
650 
651 /* editor_error */
652 static int _error_text(char const * message, int ret);
653 
editor_error(Editor * editor,char const * message,int ret)654 int editor_error(Editor * editor, char const * message, int ret)
655 {
656 #if GTK_CHECK_VERSION(2, 18, 0)
657 	if(editor == NULL)
658 		return _error_text(message, ret);
659 	gtk_label_set_text(GTK_LABEL(editor->infobar_label), message);
660 	gtk_widget_show(editor->infobar);
661 #else
662 	GtkWidget * dialog;
663 
664 	if(editor == NULL)
665 		return _error_text(message, ret);
666 	dialog = gtk_message_dialog_new(GTK_WINDOW(editor->window),
667 			GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
668 			GTK_BUTTONS_CLOSE,
669 # if GTK_CHECK_VERSION(2, 6, 0)
670 			"%s", _("Error"));
671 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
672 # endif
673 			"%s", message);
674 	gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
675 	g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(
676 				gtk_widget_destroy), NULL);
677 	gtk_widget_show(dialog);
678 #endif
679 	return ret;
680 }
681 
_error_text(char const * message,int ret)682 static int _error_text(char const * message, int ret)
683 {
684 	fprintf(stderr, "%s: %s\n", PROGNAME, message);
685 	return ret;
686 }
687 
688 
689 /* editor_close */
editor_close(Editor * editor)690 int editor_close(Editor * editor)
691 {
692 	int res;
693 
694 #ifdef DEBUG
695 	fprintf(stderr, "DEBUG: %s()\n", __func__);
696 #endif
697 	if(gtk_text_buffer_get_modified(gtk_text_view_get_buffer(GTK_TEXT_VIEW(
698 						editor->view))) == FALSE)
699 	{
700 		gtk_widget_hide(editor->window);
701 		gtk_main_quit();
702 		return 0;
703 	}
704 	res = editor_confirm(editor, _("There are unsaved changes.\n"
705 				"Discard or save them?"),
706 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
707 			GTK_STOCK_DISCARD, GTK_RESPONSE_REJECT,
708 			GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
709 	if(res == GTK_RESPONSE_CANCEL || res == GTK_RESPONSE_DELETE_EVENT)
710 		return 1;
711 	else if(res == GTK_RESPONSE_ACCEPT && editor_save(editor) != TRUE)
712 		return 1;
713 	gtk_widget_hide(editor->window);
714 	gtk_main_quit();
715 	return 0;
716 }
717 
718 
719 /* editor_copy */
editor_copy(Editor * editor)720 void editor_copy(Editor * editor)
721 {
722 	GtkTextBuffer * buffer;
723 	GtkClipboard * clipboard;
724 
725 	if(gtk_window_get_focus(GTK_WINDOW(editor->window))
726 			== editor->fi_entry)
727 	{
728 		gtk_editable_copy_clipboard(GTK_EDITABLE(editor->fi_entry));
729 		return;
730 	}
731 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
732 	clipboard = gtk_widget_get_clipboard(editor->view,
733 			GDK_SELECTION_CLIPBOARD);
734 	gtk_text_buffer_copy_clipboard(buffer, clipboard);
735 }
736 
737 
738 /* editor_cut */
editor_cut(Editor * editor)739 void editor_cut(Editor * editor)
740 {
741 	GtkTextBuffer * buffer;
742 	GtkClipboard * clipboard;
743 
744 	if(gtk_window_get_focus(GTK_WINDOW(editor->window))
745 			== editor->fi_entry)
746 	{
747 		gtk_editable_cut_clipboard(GTK_EDITABLE(editor->fi_entry));
748 		return;
749 	}
750 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
751 	clipboard = gtk_widget_get_clipboard(editor->view,
752 			GDK_SELECTION_CLIPBOARD);
753 	gtk_text_buffer_cut_clipboard(buffer, clipboard, TRUE);
754 }
755 
756 
757 /* editor_find */
editor_find(Editor * editor,char const * text)758 void editor_find(Editor * editor, char const * text)
759 {
760 	gtk_widget_show(editor->fi_dialog);
761 	if(text != NULL)
762 		gtk_entry_set_text(GTK_ENTRY(editor->fi_entry), text);
763 	gtk_widget_grab_focus(editor->fi_entry);
764 }
765 
766 
767 /* editor_insert_file */
editor_insert_file(Editor * editor,char const * filename)768 int editor_insert_file(Editor * editor, char const * filename)
769 {
770 	int ret = 0;
771 	FILE * fp;
772 	GtkTextBuffer * tbuf;
773 	char buf[BUFSIZ];
774 	size_t len;
775 	char * p;
776 	size_t rlen;
777 	size_t wlen;
778 	GError * error = NULL;
779 
780 	/* FIXME use a GIOChannel instead (with a GtkDialog or GtkStatusBar) */
781 	if((fp = fopen(filename, "r")) == NULL)
782 	{
783 		snprintf(buf, sizeof(buf), "%s: %s", filename, strerror(errno));
784 		return -editor_error(editor, buf, 1);
785 	}
786 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
787 	while((len = fread(buf, sizeof(char), sizeof(buf), fp)) > 0)
788 	{
789 #if 0
790 		if((p = g_convert(buf, len, "UTF-8", "ISO-8859-15", &rlen, &wlen, NULL)) != NULL)
791 		{
792 			gtk_text_buffer_insert_at_cursor(tbuf, p, wlen);
793 			g_free(p);
794 		}
795 		else
796 			gtk_text_buffer_insert(tbuf, &iter, buf, len);
797 #else
798 		if((p = g_locale_to_utf8(buf, len, &rlen, &wlen, &error))
799 				!= NULL)
800 			/* FIXME may lose characters */
801 			gtk_text_buffer_insert_at_cursor(tbuf, p, wlen);
802 		else
803 		{
804 			editor_error(editor, error->message, 1);
805 			g_error_free(error);
806 			error = NULL;
807 			gtk_text_buffer_insert_at_cursor(tbuf, buf, len);
808 		}
809 #endif
810 	}
811 	if(ferror(fp))
812 	{
813 		snprintf(buf, sizeof(buf), "%s: %s", filename, strerror(errno));
814 		ret = -editor_error(editor, buf, 1);
815 	}
816 	fclose(fp);
817 	return ret;
818 }
819 
820 
821 /* editor_insert_file_dialog */
editor_insert_file_dialog(Editor * editor)822 int editor_insert_file_dialog(Editor * editor)
823 {
824 	int ret;
825 	GtkWidget * dialog;
826 	GtkFileFilter * filter;
827 	char * filename = NULL;
828 
829 	dialog = gtk_file_chooser_dialog_new(_("Insert file..."),
830 			GTK_WINDOW(editor->window),
831 			GTK_FILE_CHOOSER_ACTION_OPEN,
832 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
833 			GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
834 	_helper_file_dialog_filters(dialog);
835 	if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
836 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(
837 					dialog));
838 	gtk_widget_destroy(dialog);
839 	if(filename == NULL)
840 		return 0;
841 	ret = editor_insert_file(editor, filename);
842 	g_free(filename);
843 	return ret;
844 }
845 
846 
847 /* editor_open */
editor_open(Editor * editor,char const * filename)848 int editor_open(Editor * editor, char const * filename)
849 {
850 	GtkTextBuffer * tbuf;
851 	int res;
852 	GtkTextIter iter;
853 
854 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
855 	if(gtk_text_buffer_get_modified(tbuf) == TRUE)
856 	{
857 		res = editor_confirm(editor, _("There are unsaved changes.\n"
858 					"Discard or save them?"),
859 				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
860 #if GTK_CHECK_VERSION(2, 12, 0)
861 				GTK_STOCK_DISCARD, GTK_RESPONSE_REJECT,
862 #else
863 				_("Discard"), GTK_RESPONSE_REJECT,
864 #endif
865 				GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
866 				NULL);
867 		if(res == GTK_RESPONSE_ACCEPT && editor_save(editor) != TRUE)
868 			return -1;
869 		else if(res != GTK_RESPONSE_REJECT)
870 			return 1;
871 	}
872 	gtk_text_buffer_set_text(tbuf, "", 0);
873 	editor->search = 0;
874 	if(filename == NULL)
875 	{
876 		g_free(editor->filename);
877 		editor->filename = NULL;
878 		_editor_refresh_title(editor);
879 		gtk_text_buffer_set_modified(tbuf, FALSE);
880 		return 0;
881 	}
882 	if((res = editor_insert_file(editor, filename)) != 0)
883 		return res;
884 	gtk_text_buffer_set_modified(tbuf, FALSE);
885 	/* XXX may fail */
886 	_editor_set_filename(editor, filename);
887 	_editor_refresh_title(editor);
888 	/* place the cursor back at the top of the file */
889 	gtk_text_buffer_get_start_iter(tbuf, &iter);
890 	gtk_text_buffer_place_cursor(tbuf, &iter);
891 	return 0;
892 }
893 
894 
895 /* editor_open_dialog */
editor_open_dialog(Editor * editor)896 int editor_open_dialog(Editor * editor)
897 {
898 	int ret;
899 	GtkWidget * dialog;
900 	GtkFileFilter * filter;
901 	gchar * filename = NULL;
902 
903 	dialog = gtk_file_chooser_dialog_new(_("Open file..."),
904 			GTK_WINDOW(editor->window),
905 			GTK_FILE_CHOOSER_ACTION_OPEN,
906 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
907 			GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
908 	_helper_file_dialog_filters(dialog);
909 	if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
910 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(
911 					dialog));
912 	gtk_widget_destroy(dialog);
913 	if(filename == NULL)
914 		return 1;
915 	ret = editor_open(editor, filename);
916 	g_free(filename);
917 	return ret;
918 }
919 
920 
921 /* editor_paste */
editor_paste(Editor * editor)922 void editor_paste(Editor * editor)
923 {
924 	GtkTextBuffer * buffer;
925 	GtkClipboard * clipboard;
926 
927 	if(gtk_window_get_focus(GTK_WINDOW(editor->window))
928 			== editor->fi_entry)
929 	{
930 		gtk_editable_paste_clipboard(GTK_EDITABLE(editor->fi_entry));
931 		return;
932 	}
933 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
934 	clipboard = gtk_widget_get_clipboard(editor->view,
935 			GDK_SELECTION_CLIPBOARD);
936 	gtk_text_buffer_paste_clipboard(buffer, clipboard, NULL, TRUE);
937 }
938 
939 
940 /* editor_print_dialog */
941 static void _print_dialog_on_begin_print(GtkPrintOperation * operation,
942 		GtkPrintContext * context, gpointer data);
943 static void _print_dialog_on_done(GtkPrintOperation * operation,
944 		GtkPrintOperationResult result, gpointer data);
945 static void _print_dialog_on_draw_page(GtkPrintOperation * operation,
946 		GtkPrintContext * context, gint page, gpointer data);
947 static void _print_dialog_on_end_print(GtkPrintOperation * operation,
948 		GtkPrintContext * context, gpointer data);
949 static gboolean _print_dialog_on_paginate(GtkPrintOperation * operation,
950 		GtkPrintContext * context, gpointer data);
951 
editor_print_dialog(Editor * editor)952 void editor_print_dialog(Editor * editor)
953 {
954 	GtkPrintOperation * operation;
955 	GtkPrintSettings * settings;
956 	GError * error = NULL;
957 
958 	operation = gtk_print_operation_new();
959 	gtk_print_operation_set_embed_page_setup(operation, TRUE);
960 	gtk_print_operation_set_unit(operation, GTK_UNIT_POINTS);
961 	gtk_print_operation_set_use_full_page(operation, FALSE);
962 	g_signal_connect(operation, "begin-print", G_CALLBACK(
963 				_print_dialog_on_begin_print), editor);
964 	g_signal_connect(operation, "done", G_CALLBACK(_print_dialog_on_done),
965 			editor);
966 	g_signal_connect(operation, "draw-page", G_CALLBACK(
967 				_print_dialog_on_draw_page), editor);
968 	g_signal_connect(operation, "end-print", G_CALLBACK(
969 				_print_dialog_on_end_print), editor);
970 	g_signal_connect(operation, "paginate", G_CALLBACK(
971 				_print_dialog_on_paginate), editor);
972 	settings = gtk_print_settings_new();
973 	gtk_print_operation_set_print_settings(operation, settings);
974 	gtk_print_operation_run(operation,
975 			GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
976 			GTK_WINDOW(editor->window), &error);
977 	g_object_unref(settings);
978 	g_object_unref(operation);
979 	if(error)
980 	{
981 		editor_error(editor, error->message, 1);
982 		g_error_free(error);
983 	}
984 }
985 
_print_dialog_on_begin_print(GtkPrintOperation * operation,GtkPrintContext * context,gpointer data)986 static void _print_dialog_on_begin_print(GtkPrintOperation * operation,
987 		GtkPrintContext * context, gpointer data)
988 {
989 	const gint size = 10;
990 	Editor * editor = data;
991 	char const * font;
992 
993 	/* initialize the font */
994 	font = editor_get_font(editor);
995 	editor->font = pango_font_description_from_string(font);
996 	pango_font_description_set_size(editor->font,
997 			pango_units_from_double(size));
998 	editor->font_size = size;
999 	editor->line_space = 0.0;
1000 }
1001 
_print_dialog_on_done(GtkPrintOperation * operation,GtkPrintOperationResult result,gpointer data)1002 static void _print_dialog_on_done(GtkPrintOperation * operation,
1003 		GtkPrintOperationResult result, gpointer data)
1004 {
1005 	Editor * editor = data;
1006 	GError * error = NULL;
1007 
1008 	switch(result)
1009 	{
1010 		case GTK_PRINT_OPERATION_RESULT_ERROR:
1011 			gtk_print_operation_get_error(operation, &error);
1012 			editor_error(editor, error->message, 2);
1013 			g_error_free(error);
1014 			break;
1015 		default:
1016 			break;
1017 	}
1018 }
1019 
_print_dialog_on_draw_page(GtkPrintOperation * operation,GtkPrintContext * context,gint page,gpointer data)1020 static void _print_dialog_on_draw_page(GtkPrintOperation * operation,
1021 		GtkPrintContext * context, gint page, gpointer data)
1022 {
1023 	Editor * editor = data;
1024 	GtkTextBuffer * tbuf;
1025 	cairo_t * cairo;
1026 	PangoLayout * layout;
1027 	guint i;
1028 	gboolean valid = TRUE;
1029 	GtkTextIter end;
1030 	gchar * p;
1031 
1032 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
1033 	cairo = gtk_print_context_get_cairo_context(context);
1034 	layout = gtk_print_context_create_pango_layout(context);
1035 	/* set the font */
1036 	pango_layout_set_font_description(layout, editor->font);
1037 	/* print the text */
1038 	cairo_move_to(cairo, 0.0, 0.0);
1039 	gtk_text_buffer_get_iter_at_line(tbuf, &editor->iter,
1040 			editor->line_count * page);
1041 	for(i = 0, valid = !gtk_text_iter_is_end(&editor->iter);
1042 			i < editor->line_count && valid == TRUE;
1043 			i++, valid = gtk_text_iter_forward_line(&editor->iter))
1044 	{
1045 		end = editor->iter;
1046 		if(!gtk_text_iter_ends_line(&end))
1047 			gtk_text_iter_forward_to_line_end(&end);
1048 		p = gtk_text_buffer_get_text(tbuf, &editor->iter, &end, FALSE);
1049 		/* FIXME the line may be too long */
1050 		pango_layout_set_text(layout, p, -1);
1051 		g_free(p);
1052 		pango_cairo_show_layout(cairo, layout);
1053 		cairo_rel_move_to(cairo, 0.0, editor->font_size
1054 				+ editor->line_space);
1055 	}
1056 	g_object_unref(layout);
1057 }
1058 
_print_dialog_on_end_print(GtkPrintOperation * operation,GtkPrintContext * context,gpointer data)1059 static void _print_dialog_on_end_print(GtkPrintOperation * operation,
1060 		GtkPrintContext * context, gpointer data)
1061 {
1062 	Editor * editor = data;
1063 
1064 	pango_font_description_free(editor->font);
1065 	gtk_text_view_set_editable(GTK_TEXT_VIEW(editor->view), TRUE);
1066 }
1067 
_print_dialog_on_paginate(GtkPrintOperation * operation,GtkPrintContext * context,gpointer data)1068 static gboolean _print_dialog_on_paginate(GtkPrintOperation * operation,
1069 		GtkPrintContext * context, gpointer data)
1070 {
1071 	Editor * editor = data;
1072 	GtkTextBuffer * tbuf;
1073 	gint count;
1074 	double height;
1075 
1076 	/* count the lines to print */
1077 	gtk_text_view_set_editable(GTK_TEXT_VIEW(editor->view), FALSE);
1078 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
1079 	count = gtk_text_buffer_get_line_count(tbuf);
1080 	/* count the pages required */
1081 	height = gtk_print_context_get_height(context);
1082 	editor->line_count = floor(height / (editor->font_size
1083 				+ editor->line_space));
1084 	gtk_print_operation_set_n_pages(operation,
1085 			((count - 1) / editor->line_count) + 1);
1086 	return TRUE;
1087 }
1088 
1089 
1090 /* editor_save */
editor_save(Editor * editor)1091 gboolean editor_save(Editor * editor)
1092 {
1093 	FILE * fp;
1094 	GtkTextBuffer * tbuf;
1095 	GtkTextIter start;
1096 	GtkTextIter end;
1097 	gchar * buf;
1098 	size_t len;
1099 
1100 	if(editor->filename == NULL)
1101 		return editor_save_as_dialog(editor);
1102 	if((fp = fopen(editor->filename, "w")) == NULL)
1103 	{
1104 		buf = g_strdup_printf("%s: %s", editor->filename, strerror(
1105 					errno));
1106 		editor_error(editor, buf, 1);
1107 		g_free(buf);
1108 		return FALSE;
1109 	}
1110 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
1111 	/* XXX allocating the complete file is not optimal */
1112 	gtk_text_buffer_get_start_iter(GTK_TEXT_BUFFER(tbuf), &start);
1113 	gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(tbuf), &end);
1114 	buf = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(tbuf), &start, &end,
1115 			FALSE);
1116 	len = strlen(buf);
1117 	if(fwrite(buf, sizeof(*buf), len, fp) != len)
1118 	{
1119 		g_free(buf);
1120 		fclose(fp);
1121 		editor_error(editor, _("Partial write"), 1);
1122 		return FALSE;
1123 	}
1124 	g_free(buf);
1125 	if(fclose(fp) != 0)
1126 	{
1127 		editor_error(editor, _("Partial write"), 1);
1128 		return FALSE;
1129 	}
1130 	gtk_text_buffer_set_modified(GTK_TEXT_BUFFER(tbuf), FALSE);
1131 	return TRUE;
1132 }
1133 
1134 
1135 /* editor_save_as */
editor_save_as(Editor * editor,char const * filename)1136 gboolean editor_save_as(Editor * editor, char const * filename)
1137 {
1138 	struct stat st;
1139 	GtkWidget * dialog;
1140 	int res;
1141 
1142 	if(stat(filename, &st) == 0)
1143 	{
1144 		dialog = gtk_message_dialog_new(GTK_WINDOW(editor->window),
1145 				GTK_DIALOG_MODAL
1146 				| GTK_DIALOG_DESTROY_WITH_PARENT,
1147 				GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1148 #if GTK_CHECK_VERSION(2, 6, 0)
1149 				"%s", _("Question"));
1150 		gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(
1151 					dialog),
1152 #endif
1153 				"%s",
1154 				_("This file already exists. Overwrite?"));
1155 		gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
1156 		res = gtk_dialog_run(GTK_DIALOG(dialog));
1157 		gtk_widget_destroy(dialog);
1158 		if(res == GTK_RESPONSE_NO)
1159 			return FALSE;
1160 	}
1161 	if(_editor_set_filename(editor, filename) != 0
1162 		|| editor_save(editor) != TRUE)
1163 		return FALSE;
1164 	_editor_refresh_title(editor);
1165 	return TRUE;
1166 }
1167 
1168 
1169 /* editor_save_as_dialog */
editor_save_as_dialog(Editor * editor)1170 gboolean editor_save_as_dialog(Editor * editor)
1171 {
1172 	gboolean ret;
1173 	GtkWidget * dialog;
1174 	gchar * filename = NULL;
1175 
1176 	dialog = gtk_file_chooser_dialog_new(_("Save as..."),
1177 			GTK_WINDOW(editor->window),
1178 			GTK_FILE_CHOOSER_ACTION_SAVE,
1179 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1180 			GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
1181 	if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1182 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(
1183 					dialog));
1184 	gtk_widget_destroy(dialog);
1185 	if(filename == NULL)
1186 		return FALSE;
1187 	ret = editor_save_as(editor, filename);
1188 	g_free(filename);
1189 	return ret;
1190 }
1191 
1192 
1193 /* editor_select_all */
editor_select_all(Editor * editor)1194 void editor_select_all(Editor * editor)
1195 {
1196 	GtkTextBuffer * tbuf;
1197 	GtkTextIter start;
1198 	GtkTextIter end;
1199 
1200 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
1201 	gtk_text_buffer_get_start_iter(tbuf, &start);
1202 	gtk_text_buffer_get_end_iter(tbuf, &end);
1203 	gtk_text_buffer_select_range(tbuf, &start, &end);
1204 }
1205 
1206 
1207 /* editor_show_preferences */
1208 static gboolean _preferences_on_closex(gpointer data);
1209 static void _preferences_on_response(GtkWidget * widget, gint response,
1210 		gpointer data);
1211 static void _preferences_on_apply(gpointer data);
1212 static void _preferences_on_cancel(gpointer data);
1213 static void _preferences_on_ok(gpointer data);
1214 
editor_show_preferences(Editor * editor,gboolean show)1215 void editor_show_preferences(Editor * editor, gboolean show)
1216 {
1217 	GtkWidget * vbox;
1218 	GtkWidget * hbox;
1219 	GtkWidget * widget;
1220 	GtkSizeGroup * group;
1221 	size_t i;
1222 
1223 	if(editor->pr_window != NULL)
1224 	{
1225 		if(show)
1226 			gtk_window_present(GTK_WINDOW(editor->pr_window));
1227 		else
1228 			gtk_widget_hide(editor->pr_window);
1229 		return;
1230 	}
1231 	editor->pr_window = gtk_dialog_new_with_buttons(
1232 			_("Text editor preferences"),
1233 			GTK_WINDOW(editor->window),
1234 			GTK_DIALOG_DESTROY_WITH_PARENT,
1235 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1236 			GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
1237 			GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1238 	gtk_window_set_resizable(GTK_WINDOW(editor->pr_window), FALSE);
1239 	g_signal_connect_swapped(G_OBJECT(editor->pr_window), "delete-event",
1240 			G_CALLBACK(_preferences_on_closex), editor);
1241 	g_signal_connect(G_OBJECT(editor->pr_window), "response",
1242 			G_CALLBACK(_preferences_on_response), editor);
1243 #if GTK_CHECK_VERSION(2, 14, 0)
1244 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(editor->pr_window));
1245 #else
1246 	vbox = GTK_DIALOG(editor->pr_window)->vbox;
1247 #endif
1248 	gtk_box_set_spacing(GTK_BOX(vbox), 4);
1249 	group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1250 	/* font */
1251 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
1252 	widget = gtk_label_new(_("Font:"));
1253 #if GTK_CHECK_VERSION(3, 0, 0)
1254 	g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
1255 #else
1256 	gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
1257 #endif
1258 	gtk_size_group_add_widget(group, widget);
1259 	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
1260 	editor->pr_font = gtk_font_button_new();
1261 	gtk_font_button_set_use_font(GTK_FONT_BUTTON(editor->pr_font), TRUE);
1262 	gtk_box_pack_start(GTK_BOX(hbox), editor->pr_font, TRUE, TRUE, 0);
1263 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1264 	/* wrap mode */
1265 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
1266 	widget = gtk_label_new(_("Wrap mode:"));
1267 #if GTK_CHECK_VERSION(3, 0, 0)
1268 	g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
1269 #else
1270 	gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
1271 #endif
1272 	gtk_size_group_add_widget(group, widget);
1273 	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
1274 #if GTK_CHECK_VERSION(2, 24, 0)
1275 	editor->pr_wrap = gtk_combo_box_text_new();
1276 #else
1277 	editor->pr_wrap = gtk_combo_box_new_text();
1278 #endif
1279 	for(i = 0; i < sizeof(_editor_wrap) / sizeof(*_editor_wrap); i++)
1280 #if GTK_CHECK_VERSION(2, 24, 0)
1281 		gtk_combo_box_text_append_text(
1282 				GTK_COMBO_BOX_TEXT(editor->pr_wrap),
1283 				_(_editor_wrap[i].name));
1284 #else
1285 		gtk_combo_box_append_text(GTK_COMBO_BOX(editor->pr_wrap),
1286 				_(_editor_wrap[i].name));
1287 #endif
1288 	gtk_box_pack_start(GTK_BOX(hbox), editor->pr_wrap, TRUE, TRUE, 0);
1289 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1290 	_preferences_on_cancel(editor);
1291 	gtk_widget_show_all(vbox);
1292 	if(show)
1293 		gtk_widget_show(editor->pr_window);
1294 }
1295 
_preferences_on_closex(gpointer data)1296 static gboolean _preferences_on_closex(gpointer data)
1297 {
1298 	Editor * editor = data;
1299 
1300 	_preferences_on_cancel(editor);
1301 	return TRUE;
1302 }
1303 
_preferences_on_response(GtkWidget * widget,gint response,gpointer data)1304 static void _preferences_on_response(GtkWidget * widget, gint response,
1305 		gpointer data)
1306 {
1307 	Editor * editor = data;
1308 
1309 	if(response == GTK_RESPONSE_OK)
1310 		_preferences_on_ok(editor);
1311 	else if(response == GTK_RESPONSE_APPLY)
1312 		_preferences_on_apply(editor);
1313 	else if(response == GTK_RESPONSE_CANCEL)
1314 		_preferences_on_cancel(editor);
1315 }
1316 
_preferences_on_apply(gpointer data)1317 static void _preferences_on_apply(gpointer data)
1318 {
1319 	Editor * editor = data;
1320 	char const * font;
1321 	size_t i;
1322 
1323 	font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(editor->pr_font));
1324 	editor_set_font(editor, font);
1325 	i = gtk_combo_box_get_active(GTK_COMBO_BOX(editor->pr_wrap));
1326 	if(i < sizeof(_editor_wrap) / sizeof(*_editor_wrap))
1327 		editor_set_wrap_mode(editor, _editor_wrap[i].wrap);
1328 }
1329 
_preferences_on_cancel(gpointer data)1330 static void _preferences_on_cancel(gpointer data)
1331 {
1332 	Editor * editor = data;
1333 	char const * p;
1334 	gint u = 0;
1335 
1336 	gtk_widget_hide(editor->pr_window);
1337 	gtk_font_button_set_font_name(GTK_FONT_BUTTON(editor->pr_font),
1338 			editor_get_font(editor));
1339 	if((p = config_get(editor->config, NULL, "wrap")) != NULL)
1340 		u = strtol(p, NULL, 10);
1341 	gtk_combo_box_set_active(GTK_COMBO_BOX(editor->pr_wrap), u);
1342 }
1343 
_preferences_on_ok(gpointer data)1344 static void _preferences_on_ok(gpointer data)
1345 {
1346 	Editor * editor = data;
1347 
1348 	gtk_widget_hide(editor->pr_window);
1349 	_preferences_on_apply(editor);
1350 	editor_config_save(editor);
1351 }
1352 
1353 
1354 /* editor_show_properties */
1355 static GtkWidget * _properties_widget(Editor * editor, GtkSizeGroup * group,
1356 		char const * label, GtkWidget * value);
1357 
editor_show_properties(Editor * editor,gboolean show)1358 void editor_show_properties(Editor * editor, gboolean show)
1359 {
1360 	GtkWidget * dialog;
1361 	GtkSizeGroup * hgroup;
1362 	GtkSizeGroup * vgroup;
1363 	GtkWidget * vbox;
1364 	GtkWidget * widget;
1365 	GtkTextBuffer * tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(
1366 				editor->view));
1367 	gchar * p;
1368 	gchar * q;
1369 	char buf[256];
1370 	GError * error = NULL;
1371 
1372 	if(show == FALSE)
1373 		/* XXX should really hide the window */
1374 		return;
1375 	p = (editor->filename != NULL)
1376 		? g_filename_display_basename(editor->filename) : NULL;
1377 	if(p != NULL)
1378 		snprintf(buf, sizeof(buf), _("Properties of %s"), p);
1379 	else
1380 		snprintf(buf, sizeof(buf), "%s", _("Properties"));
1381 	g_free(p);
1382 	dialog = gtk_message_dialog_new(GTK_WINDOW(editor->window),
1383 			GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1384 			GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1385 #if GTK_CHECK_VERSION(2, 6, 0)
1386 			"%s", _("Properties"));
1387 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1388 #endif
1389 			"");
1390 #if GTK_CHECK_VERSION(2, 10, 0)
1391 	gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog),
1392 			gtk_image_new_from_stock(GTK_STOCK_PROPERTIES,
1393 				GTK_ICON_SIZE_DIALOG));
1394 #endif
1395 	gtk_window_set_title(GTK_WINDOW(dialog), _("Properties"));
1396 	hgroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1397 	vgroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
1398 #if GTK_CHECK_VERSION(2, 14, 0)
1399 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1400 #else
1401 	vbox = dialog->vbox;
1402 #endif
1403 	/* filename */
1404 	p = g_strdup((editor->filename != NULL) ? editor->filename : "");
1405 	if((q = g_filename_to_utf8(p, -1, NULL, NULL, &error)) == NULL)
1406 	{
1407 		editor_error(NULL, error->message, 1);
1408 		g_error_free(error);
1409 		q = p;
1410 	}
1411 	widget = gtk_entry_new();
1412 	gtk_editable_set_editable(GTK_EDITABLE(widget), FALSE);
1413 	gtk_entry_set_text(GTK_ENTRY(widget), q);
1414 	gtk_size_group_add_widget(vgroup, widget);
1415 	g_free(p);
1416 	widget = _properties_widget(editor, hgroup, _("Filename:"), widget);
1417 	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);
1418 	/* characters */
1419 	snprintf(buf, sizeof(buf), "%u", gtk_text_buffer_get_char_count(tbuf));
1420 	widget = gtk_label_new(buf);
1421 #if GTK_CHECK_VERSION(3, 0, 0)
1422 	g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
1423 #else
1424 	gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
1425 #endif
1426 	gtk_size_group_add_widget(vgroup, widget);
1427 	widget = _properties_widget(editor, hgroup, _("Characters:"), widget);
1428 	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);
1429 	/* lines */
1430 	snprintf(buf, sizeof(buf), "%u", gtk_text_buffer_get_line_count(tbuf));
1431 	widget = gtk_label_new(buf);
1432 #if GTK_CHECK_VERSION(3, 0, 0)
1433 	g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
1434 #else
1435 	gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
1436 #endif
1437 	gtk_size_group_add_widget(vgroup, widget);
1438 	widget = _properties_widget(editor, hgroup, _("Lines:"), widget);
1439 	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);
1440 	/* FIXME implement more properties */
1441 	gtk_widget_show_all(vbox);
1442 	gtk_dialog_run(GTK_DIALOG(dialog));
1443 	gtk_widget_destroy(dialog);
1444 }
1445 
_properties_widget(Editor * editor,GtkSizeGroup * group,char const * label,GtkWidget * value)1446 static GtkWidget * _properties_widget(Editor * editor, GtkSizeGroup * group,
1447 		char const * label, GtkWidget * value)
1448 {
1449 	GtkWidget * hbox;
1450 	GtkWidget * widget;
1451 
1452 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
1453 	widget = gtk_label_new(label);
1454 	gtk_widget_override_font(widget, editor->bold);
1455 #if GTK_CHECK_VERSION(3, 0, 0)
1456 	g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
1457 #else
1458 	gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
1459 #endif
1460 	gtk_size_group_add_widget(group, widget);
1461 	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
1462 	gtk_box_pack_start(GTK_BOX(hbox), value, TRUE, TRUE, 0);
1463 	return hbox;
1464 }
1465 
1466 
1467 /* editor_unselect_all */
editor_unselect_all(Editor * editor)1468 void editor_unselect_all(Editor * editor)
1469 {
1470 	GtkTextBuffer * tbuf;
1471 	GtkTextMark * mark;
1472 	GtkTextIter iter;
1473 
1474 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
1475 	mark = gtk_text_buffer_get_mark(tbuf, "insert");
1476 	gtk_text_buffer_get_iter_at_mark(tbuf, &iter, mark);
1477 	gtk_text_buffer_select_range(tbuf, &iter, &iter);
1478 }
1479 
1480 
1481 /* private */
1482 /* functions */
1483 /* accessors */
1484 /* editor_set_filename */
_editor_set_filename(Editor * editor,char const * filename)1485 static int _editor_set_filename(Editor * editor, char const * filename)
1486 {
1487 	gchar * p = NULL;
1488 	char * q;
1489 
1490 	if(g_path_is_absolute(filename))
1491 		p = g_strdup(filename);
1492 	else if((q = getcwd(NULL, 0)) != NULL)
1493 	{
1494 		p = g_build_filename(q, filename, NULL);
1495 		free(q);
1496 	}
1497 	if(p == NULL)
1498 		return -editor_error(editor, _("Could not update the filename"),
1499 				1);
1500 	g_free(editor->filename);
1501 	editor->filename = p;
1502 	return 0;
1503 }
1504 
1505 
1506 /* editor_set_status */
_editor_set_status(Editor * editor,char const * status)1507 static void _editor_set_status(Editor * editor, char const * status)
1508 {
1509 	GtkStatusbar * sb = GTK_STATUSBAR(editor->statusbar);
1510 
1511 	if(editor->statusbar_id != 0)
1512 		gtk_statusbar_remove(sb, gtk_statusbar_get_context_id(sb, ""),
1513 				editor->statusbar_id);
1514 	editor->statusbar_id = gtk_statusbar_push(sb,
1515 			gtk_statusbar_get_context_id(sb, ""), status);
1516 }
1517 
1518 
1519 /* editor_config_filename */
_editor_config_filename(void)1520 static char * _editor_config_filename(void)
1521 {
1522 	char const * homedir;
1523 	size_t len;
1524 	char * filename;
1525 
1526 	if((homedir = getenv("HOME")) == NULL)
1527 		homedir = g_get_home_dir();
1528 	len = strlen(homedir) + 1 + sizeof(EDITOR_CONFIG_FILE);
1529 	if((filename = malloc(len)) == NULL)
1530 		return NULL;
1531 	snprintf(filename, len, "%s/%s", homedir, EDITOR_CONFIG_FILE);
1532 	return filename;
1533 }
1534 
1535 
1536 /* editor_find */
1537 static char const * _find_string(char const * big, char const * little,
1538 		gboolean sensitive);
1539 static gboolean _find_match(Editor * editor, GtkTextBuffer * buffer,
1540 		char const * buf, char const * str, size_t len);
1541 
_editor_find(Editor * editor,char const * text,gboolean sensitive,gboolean wrap)1542 static gboolean _editor_find(Editor * editor, char const * text,
1543 		gboolean sensitive, gboolean wrap)
1544 {
1545 	gboolean ret = FALSE;
1546 	size_t tlen;
1547 	GtkTextBuffer * buffer;
1548 	GtkTextIter start;
1549 	GtkTextIter end;
1550 	gchar * buf;
1551 	size_t blen;
1552 	char const * str;
1553 
1554 	if(text == NULL || (tlen = strlen(text)) == 0)
1555 		return ret;
1556 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
1557 	/* XXX highly inefficient */
1558 	gtk_text_buffer_get_start_iter(buffer, &start);
1559 	gtk_text_buffer_get_end_iter(buffer, &end);
1560 	buf = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1561 	if(buf == NULL || (blen = strlen(buf)) == 0)
1562 		return ret;
1563 	if(editor->search >= blen)
1564 		editor->search = 0;
1565 	if((str = _find_string(&buf[editor->search], text, sensitive)) != NULL)
1566 		ret = _find_match(editor, buffer, buf, str, tlen);
1567 	else if(wrap && editor->search != 0) /* wrap around */
1568 	{
1569 		buf[editor->search] = '\0';
1570 		if((str = _find_string(buf, text, sensitive)) != NULL)
1571 			ret = _find_match(editor, buffer, buf, str, tlen);
1572 	}
1573 	g_free(buf);
1574 	return ret;
1575 }
1576 
_find_string(char const * big,char const * little,gboolean sensitive)1577 static char const * _find_string(char const * big, char const * little,
1578 		gboolean sensitive)
1579 {
1580 	return sensitive ? strstr(big, little) : strcasestr(big, little);
1581 }
1582 
_find_match(Editor * editor,GtkTextBuffer * buffer,char const * buf,char const * str,size_t len)1583 static gboolean _find_match(Editor * editor, GtkTextBuffer * buffer,
1584 		char const * buf, char const * str, size_t len)
1585 {
1586 	size_t offset;
1587 	GtkTextIter start;
1588 	GtkTextIter end;
1589 
1590 	offset = str - buf;
1591 	editor->search = offset + 1;
1592 	gtk_text_buffer_get_iter_at_offset(buffer, &start, offset);
1593 	gtk_text_buffer_get_iter_at_offset(buffer, &end, offset + len);
1594 	gtk_text_buffer_select_range(buffer, &start, &end);
1595 	gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(editor->view), &start, 0.0,
1596 			FALSE, 0.0, 0.0);
1597 	return TRUE;
1598 }
1599 
1600 
1601 /* editor_refresh_title */
_editor_refresh_title(Editor * editor)1602 static void _editor_refresh_title(Editor * editor)
1603 {
1604 	char buf[256];
1605 
1606 	snprintf(buf, sizeof(buf), "%s%s", _("Text editor - "),
1607 			(editor->filename == NULL)
1608 			? _("(Untitled)") : editor->filename);
1609 	gtk_window_set_title(GTK_WINDOW(editor->window), buf);
1610 }
1611 
1612 
1613 /* callbacks */
1614 #if GTK_CHECK_VERSION(2, 16, 0)
1615 /* editor_on_find_clear */
_editor_on_find_clear(gpointer data)1616 static void _editor_on_find_clear(gpointer data)
1617 {
1618 	Editor * editor = data;
1619 
1620 	gtk_entry_set_text(GTK_ENTRY(editor->fi_entry), "");
1621 }
1622 #endif
1623 
1624 
1625 /* editor_on_find_clicked */
_editor_on_find_clicked(gpointer data)1626 static void _editor_on_find_clicked(gpointer data)
1627 {
1628 	Editor * editor = data;
1629 	char const * text;
1630 	GtkTreeModel * model = GTK_TREE_MODEL(editor->fi_store);
1631 	GtkTreeIter iter;
1632 	gboolean valid;
1633 	char * p;
1634 	int res;
1635 	gboolean sensitive;
1636 	gboolean wrap;
1637 
1638 	if((text = gtk_entry_get_text(GTK_ENTRY(editor->fi_entry))) == NULL
1639 			|| strlen(text) == 0)
1640 		return;
1641 	/* only append the text currently searched if not already known */
1642 	for(valid = gtk_tree_model_get_iter_first(model, &iter); valid == TRUE;
1643 			valid = gtk_tree_model_iter_next(model, &iter))
1644 	{
1645 		gtk_tree_model_get(model, &iter, 0, &p, -1);
1646 		res = strcmp(text, p);
1647 		free(p);
1648 		if(res == 0)
1649 			break;
1650 	}
1651 	if(valid == FALSE)
1652 	{
1653 		gtk_list_store_append(editor->fi_store, &iter);
1654 		gtk_list_store_set(editor->fi_store, &iter, 0, text, -1);
1655 	}
1656 	sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1657 				editor->fi_case));
1658 	wrap = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1659 				editor->fi_wrap));
1660 	if(_editor_find(editor, text, sensitive, wrap) == TRUE)
1661 		return;
1662 	editor_error(editor, _("Text not found"), 0);
1663 }
1664 
1665 
1666 /* editor_on_find_hide */
_editor_on_find_hide(gpointer data)1667 static void _editor_on_find_hide(gpointer data)
1668 {
1669 	Editor * editor = data;
1670 
1671 	gtk_widget_hide(editor->fi_dialog);
1672 }
1673 
1674 
1675 /* editor_on_modified */
_editor_on_modified(GtkTextBuffer * tbuf,gpointer data)1676 static void _editor_on_modified(GtkTextBuffer * tbuf, gpointer data)
1677 {
1678 	Editor * editor = data;
1679 	gboolean modified;
1680 	char const * status;
1681 
1682 	if((modified = gtk_text_buffer_get_modified(tbuf)) == TRUE)
1683 		status = _("Unsaved changes");
1684 	else if(editor->filename != NULL)
1685 		status = _("Saved");
1686 	else
1687 		status = _("Ready");
1688 	_editor_set_status(editor, status);
1689 }
1690 
1691 
1692 /* helpers */
1693 /* helper_file_dialog_filters */
_helper_file_dialog_filters(GtkWidget * dialog)1694 static void _helper_file_dialog_filters(GtkWidget * dialog)
1695 {
1696 	GtkFileFilter * filter;
1697 	char const * types[] = {
1698 		"application/x-perl",
1699 		"application/x-shellscript",
1700 		"application/xml",
1701 		"application/xslt+xml",
1702 		"text/plain" };
1703 	size_t i;
1704 
1705 	filter = gtk_file_filter_new();
1706 	gtk_file_filter_set_name(filter, _("Text files"));
1707 	for(i = 0; i < sizeof(types) / sizeof(*types); i++)
1708 		gtk_file_filter_add_mime_type(filter, types[i]);
1709 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
1710 	filter = gtk_file_filter_new();
1711 	gtk_file_filter_set_name(filter, _("All files"));
1712 	gtk_file_filter_add_pattern(filter, "*");
1713 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
1714 }
1715