1 /* Bluefish HTML Editor
2  * bookmark.c - bookmarks
3  *
4  * Copyright (C) 2003 Oskar Swida
5  * modifications (C) 2004-2013 Olivier Sessink
6  * modifications (C) 2011-2012 James Hayward
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 /*#define DEBUG*/
22 /*#define BMARKREF*/
23 
24 #include <gtk/gtk.h>
25 #include <fcntl.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <unistd.h>
31 
32 #include "bluefish.h"
33 #include "bookmark.h"
34 #include "bf_lib.h"
35 #include "bfwin_uimanager.h"
36 #include "dialog_utils.h"
37 #include "document.h"
38 #include "gtk_easy.h"
39 #include "stringlist.h"
40 
41 #ifdef BMARKREF
42 typedef struct {
43 	gint itercount;
44 } Tbmarkref;
45 
46 Tbmarkref bmarkref = { 0 };
47 
48 #endif
49 
50 /*
51 bookmarks will be loaded and saved to an arraylist (see stringlist.c). This
52 is a double linked list (GList *) with pointers to string arrays (gchar **).
53 
54 To have the GUI work with them, we convert those arrays (gchar **) into a
55 Tbmark struct. This struct will have a pointer to the array (gchar **) so
56 on change it can directly change the array as well, without any need to
57 look it up in the list.
58 
59 For the GUI, we store everything in a Gtktreestore. The treestore will have a
60 pointer to a string with the name, and it will also have a pointer to the
61 Tbmark. When the user clicks in the Gtktreeview widget, we can get
62 immediately a pointer to the Tbmark, and that has the Gtktextmark, so that
63 is very easy, and very fast!
64 
65 All normal (non-project) windows do share the same bookmarks list.
66 So they store the same Gtktreestore as well. The project functions
67 create a new gtktreestore when they convert a window (Tbfwin)
68 into a project window.
69 */
70 
71 #define BMARK_SHOW_NUM_TEXT_CHARS 40
72 #define BMARK_STORE_TEXT_NUM_CHARS 25
73 
74 enum {
75 	NAME_COLUMN,				/* bookmark name */
76 	PTR_COLUMN,					/* bookmark pointer */
77 	N_COLUMNS
78 };
79 
80 enum {
81 	BMARK_ADD_PERM_DIALOG,
82 	BMARK_RENAME_TEMP_DIALOG,
83 	BMARK_RENAME_PERM_DIALOG
84 };
85 
86 typedef struct {
87 	GtkTextMark *mark;
88 	GFile *uri;
89 	gint offset;
90 	Tdocument *doc;
91 	GtkTreeIter iter;			/* for tree view */
92 	gchar *description;
93 	gchar *text;
94 	gchar *name;
95 	gint len;					/* file length for integrity check - perhaps some hash code is needed */
96 	gboolean is_temp;
97 	gchar **strarr;				/* this is a pointer to the location where this bookmark is stored in the sessionlist,
98 								   so we can immediately change it _in_ the list */
99 } Tbmark;
100 #define BMARK(var) ((Tbmark *)(var))
101 
102 typedef struct {
103 	GtkTreeStore *bookmarkstore;	/* the treestore with the name and the pointer to the Tbmark */
104 	GHashTable *bmarkfiles;		/* a hash table with the GFile as key, and the iter in the treestore as value */
105 } Tbmarkdata;
106 #define BMARKDATA(var) ((Tbmarkdata *)(var))
107 
108 enum {
109 	BM_FMODE_FILE,
110 	BM_FMODE_PATH,
111 	BM_FMODE_URI,
112 	BM_FMODE_HOME				/* not implemented, defaults to full */
113 };
114 
115 enum {
116 	BM_SMODE_CONTENT,
117 	BM_SMODE_NAME,
118 	BM_SMODE_BOTH
119 };
120 
121 enum {
122 	BM_SEARCH_CONTENT,
123 	BM_SEARCH_NAME,
124 	BM_SEARCH_BOTH
125 };
126 
127 /* Free bookmark structure */
128 static void
bmark_free(gpointer ptr)129 bmark_free(gpointer ptr)
130 {
131 	Tbmark *m;
132 	if (ptr == NULL)
133 		return;
134 	m = BMARK(ptr);
135 	if (m->doc && m->mark) {
136 		DEBUG_MSG("bmark_free, deleting GtkTextMark %p\n", m->mark);
137 		gtk_text_buffer_delete_mark(m->doc->buffer, m->mark);
138 		m->doc = NULL;
139 	}
140 #ifdef DEBUG
141 	if (m->strarr) {
142 		DEBUG_MSG("bmark_free, NOT GOOD, strarr should be NULL here...\n");
143 	}
144 #endif
145 	DEBUG_MSG("bmark_free, unref uri %p\n", m->uri);
146 	if (m->uri)
147 		g_object_unref(m->uri);
148 	g_free(m->text);
149 	g_free(m->name);
150 	g_free(m->description);
151 	/*g_print("free bmark %p\n",m); */
152 	g_slice_free(Tbmark, m);
153 }
154 
155 static gchar *
bmark_showname(Tbfwin * bfwin,Tbmark * b)156 bmark_showname(Tbfwin * bfwin, Tbmark * b)
157 {
158 	if (b->name && strlen(b->name) > 0 && bfwin->session->bookmarks_show_mode == BM_SMODE_BOTH) {
159 		return g_strconcat(b->name, " - ", b->text, NULL);
160 	} else if ((b->name && bfwin->session->bookmarks_show_mode == BM_SMODE_NAME) || !b->text) {
161 		return g_strdup(b->name);
162 	} else {
163 		return g_strdup(b->text);
164 	}
165 }
166 
167 static gchar *
bmark_filename(Tbfwin * bfwin,GFile * uri)168 bmark_filename(Tbfwin * bfwin, GFile * uri)
169 {
170 	gchar *title;
171 	if (!uri) {
172 		g_warning("Bookmark without uri! Please report this message as a bug!\n");
173 		return g_strdup("Bug - please report");
174 	}
175 	if (g_file_has_uri_scheme(uri, "tmp")) {
176 		DEBUG_MSG("bmark_filename, have untitled document with uri scheme %s and path %s\n",
177 				  g_file_get_uri_scheme(uri), g_file_get_basename(uri));
178 		return g_file_get_basename(uri);
179 	}
180 
181 	switch (bfwin->session->bookmarks_filename_mode) {
182 	case BM_FMODE_PATH:
183 		title = g_file_get_uri(uri);
184 		break;
185 	case BM_FMODE_FILE:
186 		title = g_file_get_basename(uri);
187 		break;
188 	case BM_FMODE_URI:
189 	default:
190 		title = g_file_get_uri(uri);
191 		break;
192 	}
193 	return title;
194 }
195 
196 static GFile *
bmark_uri_from_doc(Tdocument * doc)197 bmark_uri_from_doc(Tdocument * doc)
198 {
199 	GFile *uri;
200 	if (!doc->uri) {
201 		gchar *tmp = g_strdup_printf("tmp:///%s", gtk_label_get_text(GTK_LABEL(doc->tab_label)));
202 		DEBUG_MSG("use uri '%s' for untitled document\n", tmp);
203 		uri = g_file_new_for_uri(tmp);
204 		g_free(tmp);
205 	} else {
206 		g_object_ref(doc->uri);
207 		uri = doc->uri;
208 	}
209 	return uri;
210 }
211 
212 static void
bmark_update_treestore_name(Tbfwin * bfwin)213 bmark_update_treestore_name(Tbfwin * bfwin)
214 {
215 	GtkTreeIter piter, citer;
216 	gboolean cont1, cont2;
217 	cont1 = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &piter);
218 
219 	while (cont1) {
220 		Tbmark *b = NULL;
221 		gchar *name;
222 		cont2 =
223 			gtk_tree_model_iter_children(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &citer,
224 										 &piter);
225 		/* first handle the filename of the parent */
226 		if (cont2) {
227 			gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &citer, PTR_COLUMN,
228 							   &b, -1);
229 			/* TODO: if the bookmark has a document, use the untitled number from that document */
230 			name = bmark_filename(bfwin, b->uri);
231 			gtk_tree_store_set(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, &piter, NAME_COLUMN, name, -1);
232 			g_free(name);
233 		}
234 		while (cont2) {
235 			gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &citer, PTR_COLUMN,
236 							   &b, -1);
237 			name = bmark_showname(bfwin, b);
238 			gtk_tree_store_set(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, &citer, NAME_COLUMN, name, -1);
239 			g_free(name);
240 			cont2 =
241 				gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &citer);
242 		}
243 		cont1 = gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &piter);
244 	}
245 }
246 
247 static void
bmark_update_offset_from_textmark(Tbmark * b)248 bmark_update_offset_from_textmark(Tbmark * b)
249 {
250 	if (b->doc && b->mark) {
251 		GtkTextIter it, it2;
252 		int len;
253 		gtk_text_buffer_get_iter_at_mark(b->doc->buffer, &it, b->mark);
254 		b->offset = gtk_text_iter_get_offset(&it);
255 		len = strlen(b->text);
256 		/* to aid future repositioning (if the file has changed) update the text as well */
257 		gtk_text_buffer_get_iter_at_offset(b->doc->buffer, &it2, b->offset + len);
258 		g_free(b->text);
259 		b->text = gtk_text_buffer_get_text(b->doc->buffer, &it, &it2, FALSE);
260 		DEBUG_MSG("bmark_update_offset_from_textmark, text=%s\n", b->text);
261 	}
262 }
263 
264 /*
265  * this function should use a smart sorting algorithm to find
266  * the GtkTreeIter of the bookmark *before* the place where this
267  * bookmark should be added, but the same function can be used to
268  * find the bookmarks we have to check to detect double bookmarks
269  * at the same line.
270  *
271  * returns the bookmark closest before 'offset', or the bookmark exactly at 'offset'
272  *
273  * returns NULL if we have to append this as first child to the parent
274  *
275  */
276 static Tbmark *
bmark_find_bookmark_before_offset(Tbfwin * bfwin,guint offset,GtkTreeIter * parent)277 bmark_find_bookmark_before_offset(Tbfwin * bfwin, guint offset, GtkTreeIter * parent)
278 {
279 	gboolean cont;
280 	GtkTreeIter iter;
281 	Tbmark *b1 = NULL, *b2;
282 	cont =
283 		gtk_tree_model_iter_children(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &iter,
284 									 parent);
285 	while (cont) {
286 		gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &iter, PTR_COLUMN, &b2,
287 						   -1);
288 		bmark_update_offset_from_textmark(b2);
289 		if (b2->offset > offset)
290 			return b1;
291 		b1 = b2;
292 		cont = gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &iter);
293 	}
294 	return b1;
295 }
296 
297 static void
bmark_rename_uri(Tbfwin * bfwin,Tbmark * b,Tdocument * doc)298 bmark_rename_uri(Tbfwin * bfwin, Tbmark * b, Tdocument * doc)
299 {
300 	if (b->uri)
301 		g_object_unref(b->uri);
302 	b->uri = bmark_uri_from_doc(doc);
303 	if (b->strarr != NULL) {
304 		g_free(b->strarr[2]);
305 		b->strarr[2] = g_file_get_parse_name(b->uri);
306 	}
307 }
308 
309 void
bmark_doc_renamed(Tbfwin * bfwin,Tdocument * doc)310 bmark_doc_renamed(Tbfwin * bfwin, Tdocument * doc)
311 {
312 	if (!doc->bmark_parent) {
313 		DEBUG_MSG("bmark_doc_renamed, doc %p with uri %p has no bmark_parent, return\n", doc, doc->uri);
314 		return;
315 	}
316 	GtkTreeIter tmpiter;
317 	gboolean cont;
318 	gboolean parent_renamed = FALSE;
319 
320 	cont =
321 		gtk_tree_model_iter_children(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &tmpiter,
322 									 doc->bmark_parent);
323 	while (cont) {
324 		Tbmark *b;
325 		gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &tmpiter,
326 						   PTR_COLUMN, &b, -1);
327 		cont = gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &tmpiter);
328 		if (!b) {
329 			continue;
330 		}
331 #ifdef DEVELOPMENT
332 		if (b->doc != doc) {
333 			g_warning("bmark_doc_renamed, bug, b->doc(%p)!=doc(%p)\n", b->doc, doc);
334 			g_assert_not_reached();
335 		}
336 #endif
337 
338 		if (doc->uri == b->uri || (doc->uri && g_file_equal(doc->uri, b->uri))) {
339 			return;
340 		}
341 
342 		if (!parent_renamed) {
343 			/* first remove the old uri from the hash table, but keep the GtkTreeIter stored in a variable */
344 			GtkTreeIter *newiter;
345 			newiter = g_slice_new(GtkTreeIter);
346 			*newiter = *doc->bmark_parent;
347 			g_hash_table_remove(BMARKDATA(bfwin->bmarkdata)->bmarkfiles, b->uri);
348 			doc->bmark_parent = newiter;
349 		}
350 		bmark_rename_uri(bfwin, b, doc);
351 		if (!parent_renamed) {
352 			/* now use the new b->uri as new name, and insert the new uri in the hash table */
353 			gchar *name = bmark_filename(bfwin, b->uri);
354 			gtk_tree_store_set(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, doc->bmark_parent, NAME_COLUMN,
355 							   name, -1);
356 			g_object_ref(b->uri);
357 			g_hash_table_insert(BMARKDATA(bfwin->bmarkdata)->bmarkfiles, b->uri, doc->bmark_parent);
358 			DEBUG_MSG("bmark_doc_renamed, renamed parent to %s\n", name);
359 			g_free(name);
360 			parent_renamed = TRUE;
361 		}
362 	}
363 }
364 
365 /* removes the bookmark from the session, removed the b->strarr pointer and frees it */
366 static void
bmark_unstore(Tbfwin * bfwin,Tbmark * b)367 bmark_unstore(Tbfwin * bfwin, Tbmark * b)
368 {
369 	if (bfwin->session->bmarks == NULL || b->strarr == NULL)
370 		return;
371 	DEBUG_MSG("bmark_remove, removing bookmark %p from sessionlist\n", b);
372 	bfwin->session->bmarks = g_list_remove(bfwin->session->bmarks, b->strarr);
373 	g_strfreev(b->strarr);
374 	b->strarr = NULL;
375 }
376 
377 /* this function re-uses the b->strarr if possible, otherwise it will create a new one and
378 append it to the list */
379 static void
bmark_store(Tbfwin * bfwin,Tbmark * b)380 bmark_store(Tbfwin * bfwin, Tbmark * b)
381 {
382 	gchar **strarr;
383 	if (b->is_temp) {
384 		DEBUG_MSG("bmark_store, called for temp bookmark %p ?!?! weird!!!! returning\n", b);
385 		return;
386 	}
387 	if (!b->uri) {
388 		DEBUG_MSG("bmark_store, cannot store bookmark for file without filename\n");
389 		return;
390 	}
391 	if (g_file_has_uri_scheme(b->uri, "tmp")) {
392 		DEBUG_MSG("bmark_store, cannot store bookmark for file without filename (tmp:// uri)\n");
393 		if (b->strarr) {
394 			bmark_unstore(bfwin, b);
395 		}
396 		return;
397 	}
398 	/* if there is a strarr already, we only update the fields, else we append a new one */
399 	if (b->strarr == NULL) {
400 		DEBUG_MSG("bmark_store, creating new strarr for bookmark %p\n", b);
401 		strarr = g_malloc0(sizeof(gchar *) * 7);
402 		DEBUG_MSG("name=%s, description=%s, text=%s\n", b->name, b->description, b->text);
403 		strarr[2] = g_file_get_parse_name(b->uri);
404 		strarr[4] = g_strdup(b->text);
405 	} else {
406 		DEBUG_MSG("bmark_store, bookmark %p has strarr at %p\n", b, b->strarr);
407 		strarr = b->strarr;
408 		/* free the ones we are going to update */
409 		g_free(strarr[0]);
410 		g_free(strarr[1]);
411 		g_free(strarr[3]);
412 		g_free(strarr[5]);
413 	}
414 	strarr[0] = g_strdup(b->name);
415 	strarr[1] = g_strdup(b->description);
416 
417 	if (b->doc)
418 		b->len = gtk_text_buffer_get_char_count(b->doc->buffer);
419 
420 	strarr[3] = g_strdup_printf("%d", b->offset);
421 	DEBUG_MSG("bmark_store, offset string=%s, offset int=%d\n", strarr[3], b->offset);
422 	strarr[5] = g_strdup_printf("%d", b->len);
423 	DEBUG_MSG("bmark_store, stored size=%d\n", b->len);
424 	if (b->strarr == NULL) {
425 		bfwin->session->bmarks = g_list_append(bfwin->session->bmarks, strarr);
426 		DEBUG_MSG("added new (previously unstored) bookmark to session list, list length=%d\n",
427 				  g_list_length(bfwin->session->bmarks));
428 		b->strarr = strarr;
429 	}
430 }
431 
432 /* when a users want to save the project, it's good to have updated bookmarks
433 so this function will update all arrays (strarr**)
434  */
435 void
bmark_store_all(Tbfwin * bfwin)436 bmark_store_all(Tbfwin * bfwin)
437 {
438 	/* we loop over all filename iters, and only for the ones that are opened
439 	   we loop over the children (the ones that are not open cannot be changed) */
440 	GtkTreeIter fileit;
441 	gboolean cont;
442 
443 	cont =
444 		gtk_tree_model_iter_children(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &fileit,
445 									 NULL);
446 	while (cont) {
447 		Tdocument *doc = NULL;
448 		gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &fileit, PTR_COLUMN,
449 						   &doc, -1);
450 		if (doc) {
451 			/* the document is open, so the offsets could be changed, store all permanent */
452 			GtkTreeIter bmit;
453 			gboolean cont2 =
454 				gtk_tree_model_iter_children(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore),
455 											 &bmit, &fileit);
456 			DEBUG_MSG("bmark_store_all, storing bookmarks for %s\n",
457 					  gtk_label_get_text(GTK_LABEL(doc->tab_menu)));
458 			while (cont2) {
459 				Tbmark *bmark;
460 				gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &bmit,
461 								   PTR_COLUMN, &bmark, -1);
462 				if (!bmark->is_temp) {
463 					bmark_update_offset_from_textmark(bmark);
464 					bmark_store(bfwin, bmark);
465 				}
466 				cont2 =
467 					gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore),
468 											 &bmit);
469 			}
470 		} else {
471 			DEBUG_MSG("doc not set, so not open...\n");
472 		}
473 		cont = gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &fileit);
474 	}							/* cont */
475 }
476 
477 static Tbmark *
get_bmark_at_iter(GtkTreeModel * model,GtkTreeIter * iter)478 get_bmark_at_iter(GtkTreeModel * model, GtkTreeIter * iter)
479 {
480 	Tbmark *retval = NULL;
481 	gtk_tree_model_get(model, iter, PTR_COLUMN, &retval, -1);
482 	return retval;
483 }
484 
485 /* get value from pointer column */
486 static Tbmark *
get_current_bmark(Tbfwin * bfwin)487 get_current_bmark(Tbfwin * bfwin)
488 {
489 	if (bfwin->bmark) {
490 		GtkTreePath *path;
491 		GtkTreeViewColumn *col;
492 		gtk_tree_view_get_cursor(bfwin->bmark, &path, &col);
493 		if (path != NULL) {
494 			Tbmark *retval = NULL;
495 			if (gtk_tree_path_get_depth(path) == 2) {
496 				GtkTreeIter iter;
497 				GtkTreeModel *model = gtk_tree_view_get_model(bfwin->bmark);
498 				gtk_tree_model_get_iter(model, &iter, path);
499 				retval = get_bmark_at_iter(model, &iter);
500 			} else {
501 				DEBUG_MSG("get_current_bmark, error, depth=%d\n", gtk_tree_path_get_depth(path));
502 			}
503 			gtk_tree_path_free(path);
504 			DEBUG_MSG("get_current_bmark, returning %p\n", retval);
505 			return retval;
506 		}
507 	}
508 	return NULL;
509 }
510 
511 void
bmark_add_rename_dialog(Tbfwin * bfwin,gchar * dialogtitle)512 bmark_add_rename_dialog(Tbfwin * bfwin, gchar * dialogtitle)
513 {
514 #if GTK_CHECK_VERSION(3,0,0)
515 	GtkWidget *align, *child, *dlg, *name, *desc, *button, *grid, *istemp;
516 #else
517 	GtkWidget *dlg, *name, *desc, *button, *table, *istemp;
518 #endif
519 	gint result;
520 	Tbmark *m = get_current_bmark(bfwin);
521 	if (!m)
522 		return;
523 
524 	dlg =
525 		gtk_dialog_new_with_buttons(dialogtitle, GTK_WINDOW(bfwin->main_window), GTK_DIALOG_MODAL,
526 									GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
527 	button = gtk_button_new_from_stock(GTK_STOCK_OK);
528 	gtk_widget_set_can_default(button, TRUE);
529 	gtk_dialog_add_action_widget(GTK_DIALOG(dlg), button, GTK_RESPONSE_OK);
530 
531 #if GTK_CHECK_VERSION(3,0,0)
532 	align = gtk_alignment_new(0, 0, 1, 1);
533 	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 12, 12, 6, 6);
534 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), align, FALSE, FALSE, 0);
535 
536 	grid = gtk_grid_new();
537 	gtk_grid_set_column_spacing(GTK_GRID(grid), 12);
538 	gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
539 	gtk_container_add(GTK_CONTAINER(align), grid);
540 
541 	name = gtk_entry_new();
542 	if (m->name)
543 		gtk_entry_set_text(GTK_ENTRY(name), m->name);
544 	gtk_widget_set_hexpand(name, TRUE);
545 	gtk_entry_set_activates_default(GTK_ENTRY(name), TRUE);
546 	gtk_container_add(GTK_CONTAINER(grid), name);
547 	gtk_grid_insert_column(GTK_GRID(grid), 0);
548 	gtk_grid_attach(GTK_GRID(grid), dialog_mnemonic_label_new(_("_Name:"), name), 0, 0, 1, 1);
549 
550 	desc = gtk_entry_new();
551 	if (m->description)
552 		gtk_entry_set_text(GTK_ENTRY(desc), m->description);
553 	gtk_entry_set_activates_default(GTK_ENTRY(desc), TRUE);
554 	gtk_grid_attach_next_to(GTK_GRID(grid), desc, name, GTK_POS_BOTTOM, 1, 1);
555 	gtk_grid_attach_next_to(GTK_GRID(grid), dialog_mnemonic_label_new(_("_Description:"), desc), desc,
556 							GTK_POS_LEFT, 1, 1);
557 
558 	istemp = checkbut_with_value(_("_Temporary"), m->is_temp);
559 	child = gtk_grid_get_child_at(GTK_GRID(grid), 0, 1);
560 	gtk_grid_attach_next_to(GTK_GRID(grid), istemp, child, GTK_POS_BOTTOM, 2, 1);
561 #else
562 	table = gtk_table_new(2, 3, FALSE);
563 	gtk_table_set_col_spacings(GTK_TABLE(table), 12);
564 	gtk_table_set_row_spacings(GTK_TABLE(table), 6);
565 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), table, FALSE, FALSE, 12);
566 
567 	name = dialog_entry_in_table(m->name, table, 1, 2, 0, 1);
568 	gtk_entry_set_activates_default(GTK_ENTRY(name), TRUE);
569 	dialog_mnemonic_label_in_table(_("_Name:"), name, table, 0, 1, 0, 1);
570 
571 	desc = dialog_entry_in_table(m->description, table, 1, 2, 1, 2);
572 	gtk_entry_set_activates_default(GTK_ENTRY(desc), TRUE);
573 	dialog_mnemonic_label_in_table(_("_Description:"), desc, table, 0, 1, 1, 2);
574 
575 	istemp = checkbut_with_value(_("Temporary"), m->is_temp);
576 	gtk_table_attach_defaults(GTK_TABLE(table), istemp, 0, 2, 2, 3);
577 #endif
578 
579 	gtk_window_set_default(GTK_WINDOW(dlg), button);
580 
581 	gtk_widget_show_all(dlg);
582 	result = gtk_dialog_run(GTK_DIALOG(dlg));
583 
584 	if (result == GTK_RESPONSE_OK) {
585 		gchar *tmpstr;
586 		g_free(m->name);
587 		m->name = g_strdup(gtk_entry_get_text(GTK_ENTRY(name)));
588 		g_free(m->description);
589 		m->description = g_strdup(gtk_entry_get_text(GTK_ENTRY(desc)));
590 		m->is_temp = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(istemp));
591 		tmpstr = bmark_showname(bfwin, m);
592 		gtk_tree_store_set(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, &m->iter, NAME_COLUMN, tmpstr, -1);
593 		g_free(tmpstr);
594 		if (m->is_temp) {
595 			if (m->strarr) {
596 				/* hmm previously this was not a temporary bookmark */
597 				bmark_unstore(bfwin, m);
598 			}
599 		} else {
600 			bmark_store(bfwin, m);
601 		}
602 	}
603 	gtk_widget_destroy(dlg);
604 }
605 
606 static void
bmark_activate(Tbfwin * bfwin,Tbmark * b,gboolean select_bmark)607 bmark_activate(Tbfwin * bfwin, Tbmark * b, gboolean select_bmark)
608 {
609 	GtkTextIter it;
610 
611 	if (!b)
612 		return;
613 
614 	if (!b->doc && !b->uri)
615 		return;
616 
617 	if (b->doc && b->mark) {
618 		/* recalculate offset */
619 		gtk_text_buffer_get_iter_at_mark(b->doc->buffer, &it, b->mark);
620 		b->offset = gtk_text_iter_get_offset(&it);
621 	}
622 	DEBUG_MSG("bmark_activate, bmark at %p, uri at %p\n", b, b->uri);
623 	DEBUG_MSG("bmark_activate, calling doc_new_from_uri with goto_offset %d\n", b->offset);
624 	if (b->doc) {
625 		bfwin_switch_to_document_by_pointer(BFWIN(b->doc->bfwin), b->doc);
626 		doc_select_line_by_offset(b->doc, b->offset, TRUE, TRUE);
627 	} else {
628 		doc_new_from_uri(bfwin, b->uri, NULL, FALSE, FALSE, -1, b->offset, -1, TRUE, FALSE);
629 	}
630 	/* remove selection */
631 	if (b->doc) {
632 		gtk_text_buffer_get_iter_at_mark(b->doc->buffer, &it, gtk_text_buffer_get_insert(b->doc->buffer));
633 		gtk_text_buffer_move_mark_by_name(b->doc->buffer, "selection_bound", &it);
634 		gtk_widget_grab_focus(b->doc->view);
635 	} else if (bfwin->current_document) {
636 		gtk_widget_grab_focus(bfwin->current_document->view);
637 	}
638 	if (select_bmark) {
639 		GtkTreeIter fiter;
640 		gtk_tree_model_filter_convert_child_iter_to_iter(bfwin->bmarkfilter, &fiter, &b->iter);
641 		gtk_tree_selection_select_iter(gtk_tree_view_get_selection(bfwin->bmark), &fiter);
642 	}
643 
644 }
645 
646 /*static void bmark_goto_selected(Tbfwin *bfwin) {
647 	Tbmark *b = get_current_bmark(bfwin);
648 	if (b) {
649 		bmark_activate(bfwin, b, FALSE);
650 	}
651 }*/
652 /*
653  * removes the bookmark from the treestore, and if it is the last remaining bookmark
654  * for the document, it will remove the parent iter (the filename) from the treestore as well
655  *
656  * if the parent is not removed it will return TRUE
657  * if the parent is removed, it will return FALSE
658  */
659 static gboolean
bmark_check_remove(Tbfwin * bfwin,Tbmark * b)660 bmark_check_remove(Tbfwin * bfwin, Tbmark * b)
661 {
662 	GtkTreeIter parent;
663 	/*GtkTextIter it; */
664 
665 	if (gtk_tree_model_iter_parent
666 		(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &parent, &b->iter)) {
667 		gint numchild =
668 			gtk_tree_model_iter_n_children(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore),
669 										   &parent);
670 		DEBUG_MSG("bmark_check_remove, the parent of this bookmark has %d children\n", numchild);
671 		gtk_tree_store_remove(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, &(b->iter));
672 
673 		/* Olivier, 22 may 2013: what does the next line do ?????????? seems it can be removed */
674 		/*if (b->doc) {
675 		   gtk_text_buffer_get_iter_at_mark(b->doc->buffer, &it, b->mark);
676 		   } */
677 
678 		if (numchild == 1) {
679 			GtkTextIter *tmpiter;
680 			DEBUG_MSG("bmark_check_remove, we removed the last child, now remove the parent\n");
681 			gtk_tree_store_remove(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, &parent);
682 			/* if the document is open, it should be removed from the hastable as well */
683 			tmpiter = g_hash_table_lookup(BMARKDATA(bfwin->bmarkdata)->bmarkfiles, b->uri);
684 			if (tmpiter) {
685 				DEBUG_MSG("bmark_check_remove, removing iter %p from hashtable\n", tmpiter);
686 				g_hash_table_remove(BMARKDATA(bfwin->bmarkdata)->bmarkfiles, b->uri);
687 				if (b->doc)
688 					b->doc->bmark_parent = NULL;
689 			}
690 
691 			if (b->doc && b->doc->view)
692 				bluefish_text_view_set_show_symbols_redraw(BLUEFISH_TEXT_VIEW(b->doc->view), FALSE);
693 
694 			return FALSE;
695 		}
696 	}
697 	DEBUG_MSG("bmark_check_remove, finished\n");
698 	return TRUE;
699 }
700 
701 /* *parent should be a valid GtkTreeIter pointing to a filename.  */
702 static void
bmark_del_children_backend(Tbfwin * bfwin,GtkTreeIter * parent)703 bmark_del_children_backend(Tbfwin * bfwin, GtkTreeIter * parent)
704 {
705 	GtkTreeIter tmpiter;
706 	gboolean have_parent = TRUE;
707 	while (have_parent
708 		   && gtk_tree_model_iter_children(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore),
709 										   &tmpiter, parent)) {
710 		Tbmark *b;
711 		gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &tmpiter, PTR_COLUMN,
712 						   &b, -1);
713 		if (b) {
714 			DEBUG_MSG("bmark_del_children_backend, found b=%p\n", b);
715 			have_parent = bmark_check_remove(bfwin, b);
716 			if (!b->is_temp)
717 				bmark_unstore(bfwin, b);
718 			bmark_free(b);
719 		} else {
720 			DEBUG_MSG("bmark_del_children_backend, iter without bookmark ??? LOOP WARNING!\n");
721 		}
722 	}
723 }
724 
725 static void
popup_menu_default_permanent(GtkAction * action,gpointer user_data)726 popup_menu_default_permanent(GtkAction * action, gpointer user_data)
727 {
728 	main_v->globses.bookmarks_default_store = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
729 }
730 
731 static void
popup_menu_delete(GtkAction * action,gpointer user_data)732 popup_menu_delete(GtkAction * action, gpointer user_data)
733 {
734 	Tbfwin *bfwin = BFWIN(user_data);
735 	Tbmark *bmark;
736 	gint retval;
737 	gchar *pstr;
738 	const gchar *buttons[] = { GTK_STOCK_NO, GTK_STOCK_YES, NULL };
739 	DEBUG_MSG("popup_menu_delete\n");
740 	bmark = get_current_bmark(bfwin);
741 	if (!bmark)
742 		return;
743 	/* check if it is temp mark */
744 	if (bmark->is_temp) {
745 		bmark_check_remove(bfwin, bmark);	/* check  if we should remove a filename too */
746 		bmark_free(bmark);
747 	} else {
748 		pstr = g_strdup_printf(_("Do you really want to delete %s?"), bmark->name);
749 		retval = message_dialog_new_multi(bfwin->main_window,
750 										  GTK_MESSAGE_QUESTION,
751 										  buttons, _("Delete permanent bookmark."), pstr);
752 		g_free(pstr);
753 		if (retval == 0)
754 			return;
755 		bmark_check_remove(bfwin, bmark);	/* check  if we should remove a filename too */
756 		bmark_unstore(bfwin, bmark);
757 		bmark_free(bmark);
758 	}
759 	if (bfwin->current_document)
760 		gtk_widget_grab_focus(bfwin->current_document->view);
761 }
762 
763 static void
popup_menu_delete_all(GtkAction * action,gpointer user_data)764 popup_menu_delete_all(GtkAction * action, gpointer user_data)
765 {
766 	Tbfwin *bfwin = BFWIN(user_data);
767 	GtkTreeIter tmpiter;
768 	gint retval;
769 
770 	const gchar *buttons[] = { GTK_STOCK_NO, GTK_STOCK_YES, NULL };
771 
772 
773 	if (bfwin == NULL || !bfwin->current_document)
774 		return;
775 
776 	retval = message_dialog_new_multi(bfwin->main_window,
777 									  GTK_MESSAGE_QUESTION, buttons, _("Delete all bookmarks."), NULL);
778 	if (retval == 0)
779 		return;
780 
781 	DEBUG_MSG("bmark_del_all, deleting all bookmarks!\n");
782 	while (gtk_tree_model_iter_children
783 		   (GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &tmpiter, NULL))
784 		bmark_del_children_backend(bfwin, &tmpiter);
785 
786 	gtk_widget_grab_focus(bfwin->current_document->view);
787 }
788 
789 static void
popup_menu_delete_all_doc(GtkAction * action,gpointer user_data)790 popup_menu_delete_all_doc(GtkAction * action, gpointer user_data)
791 {
792 	Tbfwin *bfwin = BFWIN(user_data);
793 
794 	if (bfwin->bmark) {
795 		GtkTreePath *path;
796 		GtkTreeViewColumn *col;
797 		gtk_tree_view_get_cursor(bfwin->bmark, &path, &col);
798 		if (path != NULL) {
799 			gchar *name;
800 			gchar *pstr;
801 			const gchar *buttons[] = { GTK_STOCK_NO, GTK_STOCK_YES, NULL };
802 			GtkTreeIter iter, realiter;
803 			GtkTreeModel *model = gtk_tree_view_get_model(bfwin->bmark);
804 			gint depth, retval;
805 			depth = gtk_tree_path_get_depth(path);
806 			if (depth == 2) {
807 				/* go up to parent */
808 				gtk_tree_path_up(path);
809 			}
810 			gtk_tree_model_get_iter(model, &iter, path);
811 			/* iter is now an iter in the filter model, not in the real backend model !!!! */
812 			gtk_tree_path_free(path);
813 			gtk_tree_model_get(model, &iter, NAME_COLUMN, &name, -1);
814 
815 			pstr = g_strdup_printf(_("Do you really want to delete all bookmarks for %s?"), name);
816 			retval = message_dialog_new_multi(bfwin->main_window,
817 											  GTK_MESSAGE_QUESTION, buttons, _("Delete bookmarks?"), pstr);
818 			g_free(pstr);
819 			if (retval == 0)
820 				return;
821 			gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &realiter, &iter);
822 			bmark_del_children_backend(bfwin, &realiter);
823 		}
824 	}
825 	if (bfwin->current_document)
826 		gtk_widget_grab_focus(bfwin->current_document->view);
827 }
828 
829 static void
popup_menu_edit(GtkAction * action,gpointer user_data)830 popup_menu_edit(GtkAction * action, gpointer user_data)
831 {
832 	Tbfwin *bfwin = BFWIN(user_data);
833 	Tbmark *bmark = get_current_bmark(bfwin);
834 
835 	if (!bmark)
836 		return;
837 
838 	bmark_add_rename_dialog(bfwin, _("Edit bookmark"));
839 }
840 
841 static void
popup_menu_show_bookmark(GtkRadioAction * action,GtkRadioAction * current,gpointer user_data)842 popup_menu_show_bookmark(GtkRadioAction * action, GtkRadioAction * current, gpointer user_data)
843 {
844 	BFWIN(user_data)->session->bookmarks_show_mode = gtk_radio_action_get_current_value(action);
845 	bmark_update_treestore_name(BFWIN(user_data));
846 }
847 
848 static void
popup_menu_show_file(GtkRadioAction * action,GtkRadioAction * current,gpointer user_data)849 popup_menu_show_file(GtkRadioAction * action, GtkRadioAction * current, gpointer user_data)
850 {
851 	BFWIN(user_data)->session->bookmarks_filename_mode = gtk_radio_action_get_current_value(action);
852 	bmark_update_treestore_name(BFWIN(user_data));
853 }
854 
855 static void
popup_search_mode_changed(GtkRadioAction * action,GtkRadioAction * current,gpointer user_data)856 popup_search_mode_changed(GtkRadioAction * action, GtkRadioAction * current, gpointer user_data)
857 {
858 	BFWIN(user_data)->session->bmarksearchmode = gtk_radio_action_get_current_value(action);
859 }
860 
861 static const gchar *bookmark_menu_ui =
862 	"<ui>"
863 	"  <popup action='BookmarkMenu'>"
864 	"    <menuitem action='EditBookmark'/>"
865 	"    <menuitem action='DeleteBookmark'/>"
866 	"    <separator/>"
867 	"    <menuitem action='DeleteAllBookmarkInDoc'/>"
868 	"    <menuitem action='DeleteAllBookmark'/>"
869 	"    <separator/>"
870 	"    <menuitem action='DefaultPermanent'/>"
871 	"    <separator/>"
872 	"    <menu action='ShowFileMenu'>"
873 	"    	<menuitem action='BookmarkFileByName'/>"
874 	"    	<menuitem action='BookmarkFileByPath'/>"
875 	"    	<menuitem action='BookmarkFileByURI'/>"
876 	"    </menu>"
877 	"    <menu action='ShowBookmarkMenu'>"
878 	"    	<menuitem action='BookmarkContent'/>"
879 	"    	<menuitem action='BookmarkName'/>"
880 	"    	<menuitem action='BookmarkNameContent'/>"
881 	"    </menu>"
882 	"  </popup>"
883 	"</ui>";
884 
885 static const gchar *bookmark_search_menu_ui =
886 	"<ui>"
887 	"  <popup action='BookmarkSearchMenu'>"
888 	"    <menuitem action='BookmarkSearchContent'/>"
889 	"    <menuitem action='BookmarkSearchName'/>"
890 	"    <menuitem action='BookmarkSearchNameContent'/>"
891 	"  </popup>"
892 	"</ui>";
893 
894 static const GtkActionEntry bookmark_actions[] = {
895 	{"BookmarkMenu", NULL, N_("Bookmark menu")},
896 	{"ShowBookmarkMenu", NULL, N_("Show Bookmark")},
897 	{"ShowFileMenu", NULL, N_("Show File")},
898 	{"BookmarkSearchMenu", NULL, N_("Bookmark search menu")},
899 	{"EditBookmark", NULL, N_("_Edit..."), NULL, N_("Edit bookmark"),
900 	 G_CALLBACK(popup_menu_edit)},
901 	{"DeleteBookmark", NULL, N_("_Delete"), NULL, N_("Delete bookmark"), G_CALLBACK(popup_menu_delete)},
902 	{"DeleteAllBookmark", NULL, N_("Delete All"), NULL, N_("Delete all bookmarks"),
903 	 G_CALLBACK(popup_menu_delete_all)},
904 	{"DeleteAllBookmarkInDoc", NULL, N_("Delete All in Document"), NULL,
905 	 N_("Delete all bookmarks in document"),
906 	 G_CALLBACK(popup_menu_delete_all_doc)}
907 };
908 
909 static const GtkToggleActionEntry bookmark_toggle_actions[] = {
910 	{"DefaultPermanent", NULL, N_("Permanent by default"), NULL, N_("Permanent by default"),
911 	 G_CALLBACK(popup_menu_default_permanent)}
912 };
913 
914 static const GtkRadioActionEntry bookmark_file_radio_actions[] = {
915 	{"BookmarkFileByName", NULL, N_("By Name"), NULL, NULL, 0},
916 	{"BookmarkFileByPath", NULL, N_("By Full Path"), NULL, NULL, 1},
917 	{"BookmarkFileByURI", NULL, N_("By Full URI"), NULL, NULL, 2},
918 };
919 
920 static const GtkRadioActionEntry bookmark_radio_actions[] = {
921 	{"BookmarkContent", NULL, N_("Content"), NULL, NULL, 0},
922 	{"BookmarkName", NULL, N_("Name"), NULL, NULL, 1},
923 	{"BookmarkNameContent", NULL, N_("Name & Content"), NULL, NULL, 2}
924 };
925 
926 static const GtkRadioActionEntry bookmark_search_radio_actions[] = {
927 	{"BookmarkSearchContent", NULL, N_("Content"), NULL, NULL, 0},
928 	{"BookmarkSearchName", NULL, N_("Name"), NULL, NULL, 1},
929 	{"BookmarkSearchNameContent", NULL, N_("Name & Content"), NULL, NULL, 2}
930 };
931 
932 static void
popup_menu_action_group_init(Tbfwin * bfwin)933 popup_menu_action_group_init(Tbfwin * bfwin)
934 {
935 	GError *error = NULL;
936 
937 	bfwin->bookmarkGroup = gtk_action_group_new("BookmarkActions");
938 	gtk_action_group_set_translation_domain(bfwin->bookmarkGroup, GETTEXT_PACKAGE);
939 	gtk_action_group_add_actions(bfwin->bookmarkGroup, bookmark_actions, G_N_ELEMENTS(bookmark_actions),
940 								 bfwin);
941 	gtk_action_group_add_toggle_actions(bfwin->bookmarkGroup, bookmark_toggle_actions,
942 										G_N_ELEMENTS(bookmark_toggle_actions), bfwin);
943 	gtk_action_group_add_radio_actions(bfwin->bookmarkGroup, bookmark_file_radio_actions,
944 									   G_N_ELEMENTS(bookmark_file_radio_actions),
945 									   bfwin->session->bookmarks_filename_mode,
946 									   G_CALLBACK(popup_menu_show_file), bfwin);
947 	gtk_action_group_add_radio_actions(bfwin->bookmarkGroup, bookmark_radio_actions,
948 									   G_N_ELEMENTS(bookmark_radio_actions),
949 									   bfwin->session->bookmarks_show_mode,
950 									   G_CALLBACK(popup_menu_show_bookmark), bfwin);
951 	gtk_action_group_add_radio_actions(bfwin->bookmarkGroup, bookmark_search_radio_actions,
952 									   G_N_ELEMENTS(bookmark_search_radio_actions),
953 									   bfwin->session->bmarksearchmode,
954 									   G_CALLBACK(popup_search_mode_changed), bfwin);
955 	gtk_ui_manager_insert_action_group(bfwin->uimanager, bfwin->bookmarkGroup, 1);
956 	g_object_unref(bfwin->bookmarkGroup);
957 
958 	gtk_ui_manager_add_ui_from_string(bfwin->uimanager, bookmark_menu_ui, -1, &error);
959 	if (error != NULL) {
960 		g_warning("building bookmark menu failed: %s", error->message);
961 		g_error_free(error);
962 	}
963 
964 	gtk_ui_manager_add_ui_from_string(bfwin->uimanager, bookmark_search_menu_ui, -1, &error);
965 	if (error != NULL) {
966 		g_warning("building bookmark search menu failed: %s", error->message);
967 		g_error_free(error);
968 	}
969 }
970 
971 static void
popup_menu(Tbfwin * bfwin,GdkEventButton * event,gboolean show_bmark_specific,gboolean show_file_specific)972 popup_menu(Tbfwin * bfwin, GdkEventButton * event, gboolean show_bmark_specific, gboolean show_file_specific)
973 {
974 	GtkWidget *menu = gtk_ui_manager_get_widget(bfwin->uimanager, "/BookmarkMenu");
975 	if (!menu)
976 		return;
977 
978 	bfwin_action_set_sensitive(bfwin->uimanager, "/BookmarkMenu/EditBookmark", show_bmark_specific);
979 	bfwin_action_set_sensitive(bfwin->uimanager, "/BookmarkMenu/DeleteBookmark", show_bmark_specific);
980 	bfwin_action_set_sensitive(bfwin->uimanager, "/BookmarkMenu/DeleteAllBookmarkInDoc", show_file_specific);
981 	bfwin_set_menu_toggle_item_from_path(bfwin->uimanager, "/BookmarkMenu/DefaultPermanent",
982 										 main_v->globses.bookmarks_default_store);
983 	gtk_radio_action_set_current_value((GtkRadioAction *)
984 									   gtk_ui_manager_get_action(bfwin->uimanager,
985 																 "/BookmarkMenu/ShowFileMenu/BookmarkFileByName"),
986 									   bfwin->session->bookmarks_filename_mode);
987 	gtk_radio_action_set_current_value((GtkRadioAction *)
988 									   gtk_ui_manager_get_action(bfwin->uimanager,
989 																 "/BookmarkMenu/ShowBookmarkMenu/BookmarkName"),
990 									   bfwin->session->bookmarks_show_mode);
991 	gtk_widget_show(menu);
992 #if GTK_CHECK_VERSION(3,22,0)
993 	gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
994 #else
995 	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
996 #endif
997 }
998 
999 static void
popup_search_menu(Tbfwin * bfwin,GdkEventButton * bevent)1000 popup_search_menu(Tbfwin * bfwin, GdkEventButton * bevent)
1001 {
1002 	GtkWidget *menu = gtk_ui_manager_get_widget(bfwin->uimanager, "/BookmarkSearchMenu");
1003 
1004 	if (menu) {
1005 		gtk_widget_show(menu);
1006 #if GTK_CHECK_VERSION(3,22,0)
1007 		gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
1008 #else
1009 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, bevent->button, bevent->time);
1010 #endif
1011 	} else
1012 		g_warning("showing bookmark search popup menu failed");
1013 }
1014 
1015 static void
bmark_search_icon_press(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEvent * event,gpointer user_data)1016 bmark_search_icon_press(GtkEntry * entry, GtkEntryIconPosition icon_pos, GdkEvent * event, gpointer user_data)
1017 {
1018 	popup_search_menu(user_data, (GdkEventButton *) event);
1019 }
1020 
1021 static void
bmark_row_activated(GtkTreeView * tree,GtkTreePath * path,GtkTreeViewColumn * column,Tbfwin * bfwin)1022 bmark_row_activated(GtkTreeView * tree, GtkTreePath * path, GtkTreeViewColumn * column, Tbfwin * bfwin)
1023 {
1024 	GtkTreeIter iter;
1025 	gtk_tree_model_get_iter(GTK_TREE_MODEL(bfwin->bmarkfilter), &iter, path);
1026 	if (gtk_tree_path_get_depth(path) == 2) {
1027 		bmark_activate(bfwin, get_bmark_at_iter(GTK_TREE_MODEL(bfwin->bmarkfilter), &iter), FALSE);
1028 	}
1029 }
1030 
1031 /* mouse click */
1032 static gboolean
bmark_event_mouseclick(GtkWidget * widget,GdkEventButton * event,Tbfwin * bfwin)1033 bmark_event_mouseclick(GtkWidget * widget, GdkEventButton * event, Tbfwin * bfwin)
1034 {
1035 	GtkTreePath *path;
1036 	gboolean show_bmark_specific = FALSE, show_file_specific = FALSE;
1037 	if (gtk_tree_view_get_path_at_pos
1038 		(GTK_TREE_VIEW(bfwin->bmark), event->x, event->y, &path, NULL, NULL, NULL)) {
1039 		if (path) {
1040 			gint depth = gtk_tree_path_get_depth(path);
1041 
1042 			if (depth == 2) {
1043 				show_bmark_specific = TRUE;
1044 				show_file_specific = TRUE;
1045 				if (event->button == 1) {
1046 					GtkTreeIter iter;
1047 					gtk_tree_model_get_iter(GTK_TREE_MODEL(bfwin->bmarkfilter), &iter, path);
1048 					bmark_activate(bfwin, get_bmark_at_iter(GTK_TREE_MODEL(bfwin->bmarkfilter), &iter),
1049 								   FALSE);
1050 				}
1051 			} else if (depth == 1) {
1052 				show_file_specific = TRUE;
1053 			}
1054 			gtk_tree_path_free(path);
1055 		}
1056 	}
1057 	if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {	/* right mouse click */
1058 		popup_menu(bfwin, event, show_bmark_specific, show_file_specific);
1059 	}
1060 
1061 	return FALSE;
1062 }
1063 
1064 /*static void bmark_selection_changed_lcb(GtkTreeSelection *treeselection,Tbfwin * bfwin) {
1065 	/ * this is not the best way to activate bookmarks. according to the gtk documentation:
1066 	Emitted whenever the selection has (possibly) changed. Please note that this signal is
1067 	mostly a hint. It may only be emitted once when a range of rows are selected, and it
1068 	may occasionally be emitted when nothing has happened.
1069 
1070 	THUS: we should better use the mouse click event to find the correct bookmark to
1071 	activate.
1072 	* /
1073 	DEBUG_MSG("bmark_selection_changed_lcb, started\n");
1074 	bmark_goto_selected(bfwin);
1075 }*/
1076 
1077 static void
bmark_first_lcb(GtkWidget * widget,Tbfwin * bfwin)1078 bmark_first_lcb(GtkWidget * widget, Tbfwin * bfwin)
1079 {
1080 	GtkTreeModel *model = GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore);
1081 	GtkTreeIter iter;
1082 
1083 	if (!CURDOC(bfwin) || !CURDOC(bfwin)->bmark_parent)
1084 		return;
1085 	DEBUG_MSG("bmark_first_lcb, started\n");
1086 	if (gtk_tree_model_iter_children(model, &iter, CURDOC(bfwin)->bmark_parent))
1087 		bmark_activate(bfwin, get_bmark_at_iter(model, &iter), TRUE);
1088 }
1089 
1090 static void
bmark_last_lcb(GtkWidget * widget,Tbfwin * bfwin)1091 bmark_last_lcb(GtkWidget * widget, Tbfwin * bfwin)
1092 {
1093 	GtkTreeModel *model = GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore);
1094 	GtkTreeIter iter;
1095 	gint num;
1096 
1097 	if (!CURDOC(bfwin) || !CURDOC(bfwin)->bmark_parent)
1098 		return;
1099 	DEBUG_MSG("bmark_last_lcb, started\n");
1100 	num = gtk_tree_model_iter_n_children(model, CURDOC(bfwin)->bmark_parent);
1101 	if (gtk_tree_model_iter_nth_child(model, &iter, CURDOC(bfwin)->bmark_parent, num - 1))
1102 		bmark_activate(bfwin, get_bmark_at_iter(model, &iter), TRUE);
1103 }
1104 
1105 static void
bmark_previous_lcb(GtkWidget * widget,Tbfwin * bfwin)1106 bmark_previous_lcb(GtkWidget * widget, Tbfwin * bfwin)
1107 {
1108 	GtkTextMark *insert;
1109 	GtkTextIter titer;
1110 	Tbmark *bmark = NULL;
1111 	gint line;
1112 
1113 	if (!CURDOC(bfwin) || !CURDOC(bfwin)->bmark_parent)
1114 		return;
1115 	DEBUG_MSG("bmark_previous_lcb, started\n");
1116 	insert = gtk_text_buffer_get_insert(CURDOC(bfwin)->buffer);
1117 	gtk_text_buffer_get_iter_at_mark(CURDOC(bfwin)->buffer, &titer, insert);
1118 	/*gtk_text_iter_set_line_offset(&titer, 0); */
1119 	gtk_text_iter_backward_line(&titer);
1120 	line = bmark_margin_get_initial_bookmark(CURDOC(bfwin), &titer, (gpointer) & bmark);
1121 	DEBUG_MSG("bmark_previous_lcb, got initial bookmark at line %d, cursor is at line %d\n", line,
1122 			  gtk_text_iter_get_line(&titer));
1123 	if (-1 == line)
1124 		return;
1125 	if (line > gtk_text_iter_get_line(&titer)) {
1126 		bmark_last_lcb(widget, bfwin);
1127 		return;
1128 	}
1129 	bmark_activate(bfwin, bmark, TRUE);
1130 }
1131 
1132 static void
bmark_next_lcb(GtkWidget * widget,Tbfwin * bfwin)1133 bmark_next_lcb(GtkWidget * widget, Tbfwin * bfwin)
1134 {
1135 	GtkTextMark *insert;
1136 	GtkTextIter titer;
1137 	Tbmark *bmark = NULL;
1138 	gint line;
1139 
1140 	if (!CURDOC(bfwin) || !CURDOC(bfwin)->bmark_parent)
1141 		return;
1142 	DEBUG_MSG("bmark_next_lcb, started\n");
1143 	insert = gtk_text_buffer_get_insert(CURDOC(bfwin)->buffer);
1144 	gtk_text_buffer_get_iter_at_mark(CURDOC(bfwin)->buffer, &titer, insert);
1145 	gtk_text_iter_forward_to_line_end(&titer);
1146 	line = bmark_margin_get_initial_bookmark(CURDOC(bfwin), &titer, (gpointer) & bmark);
1147 	if (-1 == line)
1148 		return;
1149 	if (line <= gtk_text_iter_get_line(&titer)) {
1150 		/* get the 'next' bookmark */
1151 		if (-1 == bmark_margin_get_next_bookmark(CURDOC(bfwin), (gpointer) & bmark)) {
1152 			bmark_first_lcb(widget, bfwin);
1153 			return;
1154 		}
1155 	}
1156 	bmark_activate(bfwin, bmark, TRUE);
1157 }
1158 
1159 void
bookmark_navigate(Tbfwin * bfwin,guint action)1160 bookmark_navigate(Tbfwin * bfwin, guint action)
1161 {
1162 	switch (action) {
1163 	case 1:
1164 		bmark_first_lcb(NULL, bfwin);
1165 		break;
1166 	case 2:
1167 		bmark_previous_lcb(NULL, bfwin);
1168 		break;
1169 	case 3:
1170 		bmark_next_lcb(NULL, bfwin);
1171 		break;
1172 	case 4:
1173 		bmark_last_lcb(NULL, bfwin);
1174 		break;
1175 	default:
1176 		g_warning("invalid menu action for bookmark menu, please report as bluefish bug\n ");
1177 		break;
1178 	}
1179 }
1180 
1181 static gboolean
all_children_hidden(GtkTreeModel * model,GtkTreeIter * iter,gpointer data,GtkTreeModelFilterVisibleFunc func)1182 all_children_hidden(GtkTreeModel * model, GtkTreeIter * iter, gpointer data,
1183 					GtkTreeModelFilterVisibleFunc func)
1184 {
1185 	GtkTreeIter citer;
1186 	gboolean cont = TRUE;
1187 	if (!gtk_tree_model_iter_children(model, &citer, iter)) {
1188 		return TRUE;			/* there are no children */
1189 	}
1190 	while (cont) {
1191 		if (func(model, &citer, data) == TRUE)
1192 			return FALSE;
1193 		cont = gtk_tree_model_iter_next(model, &citer);
1194 	}
1195 	return TRUE;
1196 }
1197 
1198 static gboolean
bmark_search_filter_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)1199 bmark_search_filter_func(GtkTreeModel * model, GtkTreeIter * iter, gpointer data)
1200 {
1201 	Tbfwin *bfwin = data;
1202 	Tbmark *bmark;
1203 
1204 	if (bfwin->bmark_search_prefix == NULL || bfwin->bmark_search_prefix[0] == '\0')
1205 		return TRUE;
1206 
1207 	if (gtk_tree_model_iter_has_child(model, iter)) {
1208 		if (all_children_hidden(model, iter, data, bmark_search_filter_func)) {
1209 			return FALSE;
1210 		}
1211 		return TRUE;
1212 	}
1213 	gtk_tree_model_get(model, iter, PTR_COLUMN, &bmark, -1);
1214 	if (bmark) {
1215 		switch ( /*bfwin->session->bmark_search_mode */ BM_SEARCH_BOTH) {
1216 		case BM_SEARCH_NAME:
1217 			return (bmark->name && strstr(bmark->name, bfwin->bmark_search_prefix));
1218 			break;
1219 		case BM_SEARCH_CONTENT:
1220 			return (bmark->text && strstr(bmark->text, bfwin->bmark_search_prefix));
1221 			break;
1222 		case BM_SEARCH_BOTH:
1223 			return ((bmark->text && strstr(bmark->text, bfwin->bmark_search_prefix))
1224 					|| (bmark->name && g_str_has_prefix(bmark->name, bfwin->bmark_search_prefix)));
1225 			break;
1226 		}
1227 	}
1228 	return FALSE;
1229 }
1230 
1231 static void
bmark_search_changed(GtkEditable * editable,gpointer user_data)1232 bmark_search_changed(GtkEditable * editable, gpointer user_data)
1233 {
1234 	Tbfwin *bfwin = user_data;
1235 	/* call refilter on the bmarkfilter */
1236 	g_free(bfwin->bmark_search_prefix);
1237 	bfwin->bmark_search_prefix = gtk_editable_get_chars(editable, 0, -1);
1238 	gtk_tree_model_filter_refilter(bfwin->bmarkfilter);
1239 }
1240 
1241 /* Initialize bookmarks gui for window */
1242 GtkWidget *
bmark_gui(Tbfwin * bfwin)1243 bmark_gui(Tbfwin * bfwin)
1244 {
1245 	GtkWidget *vbox, *hbox, *scroll, *entry;
1246 	GtkToolItem *but;
1247 	GtkCellRenderer *cell;
1248 	GtkTreeViewColumn *column;
1249 	DEBUG_MSG("bmark_gui, building gui for bfwin=%p\n", bfwin);
1250 	/* Tree Store is in BMARKDATA(bfwin->bmarkdata)->bookmarkstore
1251 	   Tree View is in bfwin->bmark
1252 	 */
1253 	vbox = gtk_vbox_new(FALSE, 1);
1254 	entry = gtk_entry_new();
1255 	gtk_entry_set_icon_from_stock(GTK_ENTRY(entry), GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND);
1256 	gtk_entry_set_icon_activatable(GTK_ENTRY(entry), GTK_ENTRY_ICON_PRIMARY, TRUE);
1257 	g_signal_connect(G_OBJECT(entry), "icon-press", G_CALLBACK(bmark_search_icon_press), bfwin);
1258 	g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(bmark_search_changed), bfwin);
1259 #if GTK_CHECK_VERSION(3,2,0)
1260 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 1);
1261 #endif
1262 	gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, TRUE, 0);
1263 	hbox = gtk_toolbar_new();
1264 	gtk_toolbar_set_icon_size(GTK_TOOLBAR(hbox), GTK_ICON_SIZE_MENU);
1265 	gtk_toolbar_set_style(GTK_TOOLBAR(hbox), GTK_TOOLBAR_ICONS);
1266 
1267 	but = gtk_tool_button_new(gtk_image_new_from_stock(GTK_STOCK_GOTO_TOP, GTK_ICON_SIZE_MENU), "");
1268 	g_signal_connect(G_OBJECT(but), "clicked", G_CALLBACK(bmark_first_lcb), bfwin);
1269 	gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(but), _("First bookmark"));
1270 	gtk_toolbar_insert(GTK_TOOLBAR(hbox), but, -1);
1271 	but = gtk_tool_button_new(gtk_image_new_from_stock(GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU), "");
1272 	g_signal_connect(G_OBJECT(but), "clicked", G_CALLBACK(bmark_previous_lcb), bfwin);
1273 	gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(but), _("Previous bookmark"));
1274 	gtk_toolbar_insert(GTK_TOOLBAR(hbox), but, -1);
1275 	but = gtk_tool_button_new(gtk_image_new_from_stock(GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU), "");
1276 	g_signal_connect(G_OBJECT(but), "clicked", G_CALLBACK(bmark_next_lcb), bfwin);
1277 	gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(but), _("Next bookmark"));
1278 	gtk_toolbar_insert(GTK_TOOLBAR(hbox), but, -1);
1279 	but = gtk_tool_button_new(gtk_image_new_from_stock(GTK_STOCK_GOTO_BOTTOM, GTK_ICON_SIZE_MENU), "");
1280 	g_signal_connect(G_OBJECT(but), "clicked", G_CALLBACK(bmark_last_lcb), bfwin);
1281 	gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(but), _("Last bookmark"));
1282 	gtk_toolbar_insert(GTK_TOOLBAR(hbox), but, -1);
1283 
1284 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
1285 
1286 	bfwin->bmarkfilter = (GtkTreeModelFilter *)
1287 		gtk_tree_model_filter_new(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), NULL);
1288 	gtk_tree_model_filter_set_visible_func(bfwin->bmarkfilter, bmark_search_filter_func, bfwin, NULL);
1289 	bfwin->bmark = (GtkTreeView *) gtk_tree_view_new_with_model(GTK_TREE_MODEL(bfwin->bmarkfilter));
1290 	g_object_unref(bfwin->bmarkfilter);
1291 	cell = gtk_cell_renderer_text_new();
1292 	column = gtk_tree_view_column_new_with_attributes("", cell, "text", NAME_COLUMN, NULL);
1293 	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1294 	gtk_tree_view_append_column(GTK_TREE_VIEW(bfwin->bmark), column);
1295 	gtk_widget_show_all(GTK_WIDGET(bfwin->bmark));
1296 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(bfwin->bmark), FALSE);
1297 	/*gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(bfwin->bmark), TRUE); */
1298 	scroll = gtk_scrolled_window_new(NULL, NULL);
1299 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1300 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
1301 	gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(bfwin->bmark));
1302 	gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
1303 	g_signal_connect(G_OBJECT(bfwin->bmark), "button-press-event", G_CALLBACK(bmark_event_mouseclick), bfwin);
1304 	g_signal_connect(G_OBJECT(bfwin->bmark), "row-activated", G_CALLBACK(bmark_row_activated), bfwin);
1305 	gtk_tree_view_expand_all(bfwin->bmark);
1306 	/*{
1307 	   GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(bfwin->bmark));
1308 	   gtk_tree_selection_set_mode(selection,GTK_SELECTION_BROWSE);
1309 	   g_signal_connect(G_OBJECT(selection), "changed",G_CALLBACK(bmark_selection_changed_lcb), bfwin);
1310 	   } */
1311 
1312 	if (!bfwin->bookmarkGroup)
1313 		popup_menu_action_group_init(bfwin);
1314 
1315 	return vbox;
1316 }
1317 
1318 /**
1319  * bmark_get_iter_at_tree_position:
1320  *
1321  * determine bookmark's location in the tree and  insert - result GtkTreeIter is stored in m->iter
1322  */
1323 static void
bmark_get_iter_at_tree_position(Tbfwin * bfwin,Tbmark * m)1324 bmark_get_iter_at_tree_position(Tbfwin * bfwin, Tbmark * m)
1325 {
1326 	GtkTreeIter *parent;
1327 	gpointer ptr;
1328 	DEBUG_MSG("bmark_get_iter_at_tree_position, started\n");
1329 	ptr = g_hash_table_lookup(BMARKDATA(bfwin->bmarkdata)->bmarkfiles, m->uri);
1330 	DEBUG_MSG("bmark_get_iter_at_tree_position, found %p in hashtable %p\n", ptr,
1331 			  BMARKDATA(bfwin->bmarkdata)->bmarkfiles);
1332 	if (ptr == NULL) {			/* closed document or bookmarks never set */
1333 		gchar *title;
1334 		parent = g_slice_new0(GtkTreeIter);
1335 #ifdef BMARKREF
1336 		bmarkref.itercount++;
1337 		g_print("bmark_get_iter_at_tree_position, itercount=%d\n", bmarkref.itercount);
1338 #endif
1339 		/* we should sort the document names in the treestore */
1340 		title = bmark_filename(bfwin, m->uri);
1341 		DEBUG_MSG("insert parent with name %s and doc=%p in treestore %p\n", title, m->doc,
1342 				  BMARKDATA(bfwin->bmarkdata)->bookmarkstore);
1343 		gtk_tree_store_insert_with_values(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, parent, NULL, 0,
1344 										  NAME_COLUMN, title, PTR_COLUMN, m->doc, -1);
1345 /*		gtk_tree_store_append(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, parent, NULL);
1346 		gtk_tree_store_set(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, parent, NAME_COLUMN, title, PTR_COLUMN, m->doc, -1);*/
1347 		g_free(title);
1348 		if (m->doc != NULL) {
1349 			m->doc->bmark_parent = parent;
1350 		}
1351 		DEBUG_MSG("bmark_get_iter_at_tree_position, appending parent %p in hashtable %p\n", parent,
1352 				  BMARKDATA(bfwin->bmarkdata)->bmarkfiles);
1353 		/* the hash table frees the key, but not the value, on destroy */
1354 		g_object_ref(m->uri);
1355 		g_hash_table_insert(BMARKDATA(bfwin->bmarkdata)->bmarkfiles, m->uri, parent);
1356 	} else {
1357 		parent = (GtkTreeIter *) ptr;
1358 	}
1359 	gtk_tree_store_prepend(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, &m->iter, parent);
1360 }
1361 
1362 static gint
bmark_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)1363 bmark_sort_func(GtkTreeModel * model, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data)
1364 {
1365 	GtkTreeIter tmp;
1366 	if (gtk_tree_model_iter_parent(model, &tmp, a) == FALSE) {
1367 		gint retval;
1368 		gchar *name_a, *name_b;
1369 		gtk_tree_model_get(model, a, NAME_COLUMN, &name_a, -1);
1370 		gtk_tree_model_get(model, b, NAME_COLUMN, &name_b, -1);
1371 		retval = g_strcmp0(name_a, name_b);
1372 		g_free(name_a);
1373 		g_free(name_b);
1374 		return retval;
1375 	} else {
1376 		Tbmark *bmark_a, *bmark_b;
1377 		gtk_tree_model_get(model, a, PTR_COLUMN, &bmark_a, -1);
1378 		gtk_tree_model_get(model, b, PTR_COLUMN, &bmark_b, -1);
1379 		if (bmark_a && bmark_b) {
1380 			return bmark_a->offset - bmark_b->offset;
1381 		} else {
1382 			return (gint) (bmark_a - bmark_b);
1383 		}
1384 	}
1385 }
1386 
1387 static void
bmark_hash_value_free(gpointer data)1388 bmark_hash_value_free(gpointer data)
1389 {
1390 	DEBUG_MSG("bmark_hash_value_free, free iter %p\n", data);
1391 #ifdef BMARKREF
1392 	bmarkref.itercount--;
1393 	g_print("bmark_hash_value_free, itercount=%d\n", bmarkref.itercount);
1394 #endif
1395 	g_slice_free(GtkTreeIter, data);
1396 }
1397 
1398 static void
bmark_hash_key_free(gpointer data)1399 bmark_hash_key_free(gpointer data)
1400 {
1401 	if (!data)
1402 		return;
1403 	DEBUG_MSG("bmark_hash_key_free, unref %p\n", data);
1404 	g_object_unref(data);
1405 }
1406 
1407 /*
1408  * this function is used to create the global main_v->bookmarkstore
1409  * as well as the project bookmarkstores
1410  */
1411 gpointer
bookmark_data_new(void)1412 bookmark_data_new(void)
1413 {
1414 	Tbmarkdata *bmd;
1415 	bmd = g_new0(Tbmarkdata, 1);
1416 	bmd->bookmarkstore = gtk_tree_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER);
1417 	gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(bmd->bookmarkstore), bmark_sort_func, bmd,
1418 											NULL);
1419 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(bmd->bookmarkstore),
1420 										 GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
1421 	bmd->bmarkfiles =
1422 		g_hash_table_new_full(g_file_hash, (GEqualFunc) g_file_equal, bmark_hash_key_free,
1423 							  bmark_hash_value_free);
1424 	DEBUG_MSG("bookmark_data_new, created bookmarkstore at %p\n", bmd->bookmarkstore);
1425 	return bmd;
1426 }
1427 
1428 /* used to clean up the project bookmarkdata */
1429 gpointer
bookmark_data_cleanup(gpointer data)1430 bookmark_data_cleanup(gpointer data)
1431 {
1432 	Tbmarkdata *bmd = BMARKDATA(data);
1433 	GtkTreeIter fileit;
1434 	gboolean cont;
1435 	/*walk the treestore and free all Tbmark's in the pointer columns */
1436 	DEBUG_MSG("bookmark_data_cleanup bmarkdata %p\n", bmd);
1437 	cont = gtk_tree_model_iter_children(GTK_TREE_MODEL(bmd->bookmarkstore), &fileit, NULL);
1438 	while (cont) {				/* walk the toplevel */
1439 		GtkTreeIter bmit;
1440 		gboolean cont2 = gtk_tree_model_iter_children(GTK_TREE_MODEL(bmd->bookmarkstore), &bmit, &fileit);
1441 		while (cont2) {
1442 			Tbmark *bmark;
1443 			gtk_tree_model_get(GTK_TREE_MODEL(bmd->bookmarkstore), &bmit, PTR_COLUMN, &bmark, -1);
1444 			bmark->strarr = NULL;
1445 			if (bmark->doc)
1446 				bmark->doc->bmark_parent = NULL;
1447 			bmark_free(bmark);
1448 			cont2 = gtk_tree_model_iter_next(GTK_TREE_MODEL(bmd->bookmarkstore), &bmit);
1449 		}
1450 		cont = gtk_tree_model_iter_next(GTK_TREE_MODEL(bmd->bookmarkstore), &fileit);
1451 	}
1452 	g_object_unref(bmd->bookmarkstore);
1453 	g_hash_table_destroy(bmd->bmarkfiles);
1454 	g_free(bmd);
1455 	return NULL;
1456 }
1457 
1458 /* this function will load the bookmarks
1459  * from bfwin->session->bmarks and parse
1460  * them into treestore BMARKDATA(bfwin->bmarkdata)->bookmarkstore
1461  *
1462  * it is called from bluefish.c for the first window (global bookmarks)
1463  * and from project.c during opening a project
1464  *
1465  * this function should ALSO check all douments that are
1466  * opened (bfwin->documentlist) if they have bookmarks !!
1467  */
1468 void
bmark_reload(Tbfwin * bfwin)1469 bmark_reload(Tbfwin * bfwin)
1470 {
1471 	GFile *cacheduri = NULL;
1472 	GList *tmplist;
1473 
1474 	DEBUG_MSG("bmark_reload for bfwin %p\n", bfwin);
1475 	bmark_store_all(bfwin);
1476 
1477 	tmplist = g_list_first(bfwin->session->bmarks);
1478 	while (tmplist) {
1479 		gchar **items = (gchar **) tmplist->data;
1480 		if (items && g_strv_length(items) == 6) {
1481 			gchar *ptr;
1482 			Tbmark *b;
1483 			b = g_slice_new0(Tbmark);
1484 			/*g_print("bmark_reload, alloc bmark %p\n",b); */
1485 			b->name = g_strdup(items[0]);
1486 			b->description = g_strdup(items[1]);
1487 			/* convert old (Bf 1.0) bookmarks to new bookmarks with uri's */
1488 			if (strchr(items[2], ':') == NULL) {
1489 				gchar *tmp;
1490 				tmp = g_strconcat("file://", items[2], NULL);
1491 				b->uri = g_file_parse_name(tmp);
1492 				g_free(tmp);
1493 			} else {
1494 				b->uri = g_file_parse_name(items[2]);
1495 			}
1496 			/* because the bookmark list is usually sorted, we try to cache the uri's and consume less memory */
1497 			if (cacheduri && (cacheduri == b->uri || g_file_equal(cacheduri, b->uri))) {
1498 				DEBUG_MSG("bmark_reload, uri %p and %p are identical, unref %p and use %p\n", cacheduri,
1499 						  b->uri, b->uri, cacheduri);
1500 				g_object_unref(b->uri);
1501 				b->uri = g_object_ref(cacheduri);;
1502 			} else {
1503 				DEBUG_MSG("bmark_reload, new uri %p\n", b->uri);
1504 				cacheduri = b->uri;
1505 			}
1506 
1507 			b->offset = atoi(items[3]);
1508 			b->text = g_strdup(items[4]);
1509 			b->len = atoi(items[5]);
1510 			b->strarr = items;
1511 			DEBUG_MSG("bmark_reload, loaded bookmark %p for uri=%pat offset %d with text %s\n", b,
1512 					  b->uri, b->offset, b->text);
1513 			bmark_get_iter_at_tree_position(bfwin, b);
1514 			ptr = bmark_showname(bfwin, b);
1515 			gtk_tree_store_set(BMARKDATA(bfwin->bmarkdata)->bookmarkstore, &(b->iter), NAME_COLUMN, ptr,
1516 							   PTR_COLUMN, b, -1);
1517 			g_free(ptr);
1518 		}
1519 		tmplist = g_list_next(tmplist);
1520 	}
1521 #ifdef WALKTREE
1522 	/* walk over all bookmarks and print them to stdout */
1523 	{
1524 		GtkTreeIter iter;
1525 		gboolean cont;
1526 		cont =
1527 			gtk_tree_model_get_iter_first(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &iter);
1528 		while (cont) {
1529 			gchar *name;
1530 			gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &iter, NAME_COLUMN,
1531 							   &name, -1);
1532 			g_print("walk bookmarks, got name %s\n", name);
1533 			g_free(name);
1534 			cont =
1535 				gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &iter);
1536 		}
1537 	}
1538 #endif
1539 }
1540 
1541 /*
1542  * this function will simply call
1543  * gtk_tree_view_set_model() to connect the treeview
1544  * to the new treestore, used in unloading and
1545  * loading of projects
1546  */
1547 void
bmark_set_store(Tbfwin * bfwin)1548 bmark_set_store(Tbfwin * bfwin)
1549 {
1550 	DEBUG_MSG("bmark_set_store set store %p for bfwin %p\n", BMARKDATA(bfwin->bmarkdata)->bookmarkstore,
1551 			  bfwin);
1552 	if (BMARKDATA(bfwin->bmarkdata)->bookmarkstore && bfwin->bmark) {
1553 		bfwin->bmarkfilter = (GtkTreeModelFilter *)
1554 			gtk_tree_model_filter_new(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), NULL);
1555 		gtk_tree_model_filter_set_visible_func(bfwin->bmarkfilter, bmark_search_filter_func, bfwin, NULL);
1556 		gtk_tree_view_set_model(bfwin->bmark, GTK_TREE_MODEL(bfwin->bmarkfilter));
1557 		g_object_unref(bfwin->bmarkfilter);
1558 	}
1559 }
1560 
1561 /* the Tdocument will be closed, but the bookmark should stay in the treestore */
1562 void
bmark_clean_for_doc(Tdocument * doc)1563 bmark_clean_for_doc(Tdocument * doc)
1564 {
1565 	GtkTreeIter tmpiter;
1566 	GtkTreePath *path;
1567 	gboolean cont;
1568 
1569 	if (doc->bmark_parent == NULL)
1570 		return;
1571 
1572 	if (BFWIN(doc->bfwin)->bmarkdata == NULL)
1573 		return;
1574 
1575 	DEBUG_MSG("bmark_clean_for_doc, doc=%p, bfwin=%p, bmarkdata=%p, getting children for parent_iter=%p\n",
1576 			  doc, doc->bfwin, BFWIN(doc->bfwin)->bmarkdata, doc->bmark_parent);
1577 	/* a segfault is reported here, coming from a document save and close */
1578 	cont =
1579 		gtk_tree_model_iter_children(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore),
1580 									 &tmpiter, doc->bmark_parent);
1581 	while (cont) {
1582 		Tbmark *b = NULL;
1583 		DEBUG_MSG("bmark_clean_for_doc, getting bookmark for first child\n");
1584 		gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &tmpiter,
1585 						   PTR_COLUMN, &b, -1);
1586 		if (b) {
1587 			bmark_update_offset_from_textmark(b);
1588 			DEBUG_MSG
1589 				("bmark_clean_for_doc, bookmark=%p, new offset=%d, now deleting GtkTextMark %p from TextBuffer\n",
1590 				 b, b->offset, b->mark);
1591 			gtk_text_buffer_delete_mark(doc->buffer, b->mark);
1592 			if (doc->fileinfo)
1593 				b->len = gtk_text_buffer_get_char_count(doc->buffer);
1594 			b->mark = NULL;
1595 			b->doc = NULL;
1596 			if (!b->is_temp) {
1597 				bmark_store(doc->bfwin, b);
1598 			}
1599 		}
1600 		cont =
1601 			gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore),
1602 									 &tmpiter);
1603 	}							/* cont */
1604 	/* now unset the Tdocument* in the second column */
1605 	DEBUG_MSG("bmark_clean_for_doc, unsetting and freeing parent_iter %p for doc %p\n", doc->bmark_parent,
1606 			  doc);
1607 	gtk_tree_store_set(GTK_TREE_STORE(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore),
1608 					   doc->bmark_parent, PTR_COLUMN, NULL, -1);
1609 	gtk_tree_model_filter_convert_child_iter_to_iter(BFWIN(doc->bfwin)->bmarkfilter, &tmpiter,
1610 													 doc->bmark_parent);
1611 	doc->bmark_parent = NULL;
1612 	path = gtk_tree_model_get_path(GTK_TREE_MODEL(BFWIN(doc->bfwin)->bmarkfilter), &tmpiter);
1613 	gtk_tree_view_collapse_row(BFWIN(doc->bfwin)->bmark, path);
1614 	gtk_tree_path_free(path);
1615 }
1616 
1617 static gboolean
bookmark_reposition(Tbmark * mark,gint offset)1618 bookmark_reposition(Tbmark * mark, gint offset)
1619 {
1620 	gint doclen = gtk_text_buffer_get_char_count(mark->doc->buffer);
1621 	gint bandwidth = offset > 0 ? 2 * offset : -2 * offset;
1622 	if (bandwidth < (5 * strlen(mark->text)))
1623 		bandwidth = 5 * strlen(mark->text);
1624 	/* search for the bookmark near the old positions */
1625 
1626 	while (TRUE) {
1627 		GtkTextIter its, ite, /*starr,end */ itrs, itre;	/* resultstart, resultend */
1628 		gint startpos;
1629 		startpos = mark->offset + offset - bandwidth / 2;
1630 		if (startpos < 0)
1631 			startpos = 0;
1632 		DEBUG_MSG("bookmark_reposition, searching from %d to %d for %s\n", startpos, startpos + bandwidth,
1633 				  mark->text);
1634 		gtk_text_buffer_get_iter_at_offset(mark->doc->buffer, &its, startpos);
1635 		gtk_text_buffer_get_iter_at_offset(mark->doc->buffer, &ite, startpos + bandwidth);
1636 		if (gtk_text_iter_forward_search(&its, mark->text, GTK_TEXT_SEARCH_TEXT_ONLY, &itrs, &itre, &ite)) {
1637 			/* found !!!!!!! */
1638 			DEBUG_MSG("bookmark_reposition, found result! resposition from %d to %d\n", mark->offset,
1639 					  gtk_text_iter_get_offset(&itrs));
1640 			mark->offset = gtk_text_iter_get_offset(&itrs);
1641 			return TRUE;
1642 		}
1643 		if (bandwidth > doclen) {
1644 			DEBUG_MSG("bookmark_reposition, no result for %s, original offset %d\n", mark->text,
1645 					  mark->offset);
1646 			return FALSE;
1647 		}
1648 		bandwidth *= 2;
1649 	}
1650 }
1651 
1652 static gboolean
bookmark_needs_repositioning(Tbmark * mark,GtkTextIter * it)1653 bookmark_needs_repositioning(Tbmark * mark, GtkTextIter * it)
1654 {
1655 	GtkTextIter it2;
1656 	gboolean retval;
1657 	gchar *tmpstr;
1658 	/* check the content at the bookmark */
1659 	gtk_text_buffer_get_iter_at_offset(mark->doc->buffer, &it2, mark->offset + strlen(mark->text));
1660 	tmpstr = gtk_text_buffer_get_text(mark->doc->buffer, it, &it2, FALSE);
1661 	DEBUG_MSG("original offset %d, compare %s and %s\n", mark->offset, tmpstr, mark->text);
1662 	retval = (strcmp(tmpstr, mark->text) != 0);
1663 	DEBUG_MSG("bookmark_needs_repositioning, reposition=%d,text='%s', tmpstr='%s'\n", retval, mark->text,
1664 			  tmpstr);
1665 	g_free(tmpstr);
1666 	return retval;
1667 }
1668 
1669 /*
1670  * this function will check is this document needs any bookmarks, and set the
1671  * doc->bmark_parent if needed
1672  *
1673  * if there are bookmarks, the bookmark GtkTextMark's will be inserted
1674  *
1675  * if check_position is TRUE, the content of the bookmark will be checked, and if
1676  * changed, the offset will be re-positioned
1677  *
1678  */
1679 void
bmark_set_for_doc(Tdocument * doc,gboolean check_positions)1680 bmark_set_for_doc(Tdocument * doc, gboolean check_positions)
1681 {
1682 	gboolean cont2;
1683 	GtkTreeIter child;
1684 	GtkTreePath *path;
1685 
1686 	if (!doc->uri) {
1687 		DEBUG_MSG("bmark_set_for_doc, document %p does not have a filename, returning\n", doc);
1688 		return;
1689 	}
1690 
1691 	DEBUG_MSG("bmark_set_for_doc, doc=%p, filename=%s\n", doc, gtk_label_get_text(GTK_LABEL(doc->tab_menu)));
1692 /*	if (!BFWIN(doc->bfwin)->bmark) {
1693 		DEBUG_MSG("bmark_set_for_doc, no leftpanel, not implemented yet!!\n");
1694 		return;
1695 	}*/
1696 	if (doc->bmark_parent) {
1697 		DEBUG_MSG("this document (%p) already has a bmark_parent (%p) why is this function called?\n", doc,
1698 				  doc->bmark_parent);
1699 		return;
1700 	}
1701 
1702 	doc->bmark_parent = g_hash_table_lookup(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bmarkfiles, doc->uri);
1703 	if (!doc->bmark_parent)
1704 		return;
1705 
1706 	/*g_print("bmark_set_for_doc, we found a bookmark for document %s at offset=%d!\n",gtk_label_get_text(GTK_LABEL(doc->tab_menu)),mark->offset); */
1707 	gtk_tree_store_set(GTK_TREE_STORE(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore),
1708 					   doc->bmark_parent, PTR_COLUMN, doc, -1);
1709 
1710 	cont2 =
1711 		gtk_tree_model_iter_children(GTK_TREE_MODEL
1712 									 (BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &child,
1713 									 doc->bmark_parent);
1714 	while (cont2) {				/* loop the bookmarks for this document  */
1715 		Tbmark *mark = NULL;
1716 		gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &child,
1717 						   PTR_COLUMN, &mark, -1);
1718 		if (mark) {
1719 			GtkTextIter it;
1720 			mark->doc = doc;
1721 			DEBUG_MSG("bmark_set_for_doc, next bookmark at offset=%d!\n", mark->offset);
1722 			gtk_text_buffer_get_iter_at_offset(doc->buffer, &it, mark->offset);
1723 			if (check_positions && bookmark_needs_repositioning(mark, &it)) {	/* repositioning required ! */
1724 				if (bookmark_reposition(mark, gtk_text_buffer_get_char_count(doc->buffer) - mark->len)) {
1725 					gtk_text_buffer_get_iter_at_offset(doc->buffer, &it, mark->offset);
1726 				} else {
1727 					/* BUG: bookmark not restored, what to do now ???? - just put it where it was ??  */
1728 				}
1729 			}
1730 			mark->mark = gtk_text_buffer_create_mark(doc->buffer, NULL, &it, TRUE);
1731 			DEBUG_MSG("bmark_set_for_doc, create GtkTextMark for bmark %p at %p\n", mark, mark->mark);
1732 		}
1733 		cont2 =
1734 			gtk_tree_model_iter_next(GTK_TREE_MODEL
1735 									 (BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &child);
1736 	}
1737 
1738 	/* expand it */
1739 	gtk_tree_model_filter_convert_child_iter_to_iter(BFWIN(doc->bfwin)->bmarkfilter, &child,
1740 													 doc->bmark_parent);
1741 	path = gtk_tree_model_get_path(GTK_TREE_MODEL(BFWIN(doc->bfwin)->bmarkfilter), &child);
1742 	gtk_tree_view_expand_row(BFWIN(doc->bfwin)->bmark, path, TRUE);
1743 	gtk_tree_path_free(path);
1744 	bluefish_text_view_set_show_symbols_redraw(BLUEFISH_TEXT_VIEW(doc->view), TRUE);
1745 }
1746 
1747 /* this is called by the editor widget to show bookmarks in the left margin.
1748 returns a line number for the Tbmark that bmark points to, or -1 if there is no bmark  */
1749 gint
bmark_margin_get_next_bookmark(Tdocument * doc,gpointer * bmark)1750 bmark_margin_get_next_bookmark(Tdocument * doc, gpointer * bmark)
1751 {
1752 	gboolean cont;
1753 	GtkTextIter textit;
1754 	GtkTreeIter treeit = ((Tbmark *) * bmark)->iter;
1755 	cont =
1756 		gtk_tree_model_iter_next(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore),
1757 								 &treeit);
1758 	if (!cont) {
1759 		return -1;
1760 	}
1761 	gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &treeit,
1762 					   PTR_COLUMN, bmark, -1);
1763 	gtk_text_buffer_get_iter_at_mark(doc->buffer, &textit, ((Tbmark *) * bmark)->mark);
1764 	return gtk_text_iter_get_line(&textit);
1765 }
1766 
1767 /* this is called by the editor widget to show bookmarks in the left margin.
1768  returns a line number for the Tbmark that bmark points to, or -1 if there is no bmark */
1769 gint
bmark_margin_get_initial_bookmark(Tdocument * doc,GtkTextIter * fromit,gpointer * bmark)1770 bmark_margin_get_initial_bookmark(Tdocument * doc, GtkTextIter * fromit, gpointer * bmark)
1771 {
1772 	guint offset;
1773 	GtkTextIter textit;
1774 	if (!doc->bmark_parent) {
1775 		return -1;
1776 	}
1777 	offset = gtk_text_iter_get_offset(fromit);
1778 	*bmark = bmark_find_bookmark_before_offset(BFWIN(doc->bfwin), offset, doc->bmark_parent);	/* returns NULL if there is no existing bookmark *before* offset */
1779 	if (!*bmark) {
1780 		GtkTreeIter treeit;
1781 		gboolean retval =
1782 			gtk_tree_model_iter_children(GTK_TREE_MODEL
1783 										 (BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &treeit,
1784 										 doc->bmark_parent);
1785 		if (!retval) {
1786 			return -1;
1787 		}
1788 		gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &treeit,
1789 						   PTR_COLUMN, bmark, -1);
1790 	}
1791 	gtk_text_buffer_get_iter_at_mark(doc->buffer, &textit, ((Tbmark *) * bmark)->mark);
1792 	return gtk_text_iter_get_line(&textit);
1793 }
1794 
1795 /* this function will simply add the bookmark as defined in the arguments
1796  *
1797  * will use offset if itoffset is NULL
1798  * will use itoffset if not NULL
1799  */
1800 static Tbmark *
bmark_add_backend(Tdocument * doc,GtkTextIter * itoffset,gint offset,const gchar * name,const gchar * text,gboolean is_temp)1801 bmark_add_backend(Tdocument * doc, GtkTextIter * itoffset, gint offset, const gchar * name,
1802 				  const gchar * text, gboolean is_temp)
1803 {
1804 	Tbmark *m;
1805 	gchar *displaytext = NULL;
1806 	GtkTextIter it;
1807 	m = g_slice_new0(Tbmark);
1808 	/*g_print("bmark_add_backend, alloc bmark %p\n",m); */
1809 	m->doc = doc;
1810 
1811 	if (itoffset) {
1812 		it = *itoffset;
1813 		m->offset = gtk_text_iter_get_offset(&it);
1814 	} else {
1815 		gtk_text_buffer_get_iter_at_offset(doc->buffer, &it, offset);
1816 		m->offset = offset;
1817 	}
1818 
1819 	m->mark = gtk_text_buffer_create_mark(doc->buffer, NULL, &it, TRUE);
1820 	DEBUG_MSG("bmark_add_backend, mark=%p, create GtkTextMark %p\n", m, m->mark);
1821 	m->uri = bmark_uri_from_doc(doc);
1822 	m->is_temp = is_temp;
1823 	m->text = g_strdup(text);
1824 	m->name = (name) ? g_strdup(name) : g_strdup("");
1825 	m->description = g_strdup("");
1826 
1827 	/* insert into tree */
1828 	bmark_get_iter_at_tree_position(doc->bfwin, m);
1829 	displaytext = bmark_showname(doc->bfwin, m);
1830 	gtk_tree_store_set(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore, &m->iter, NAME_COLUMN,
1831 					   displaytext, PTR_COLUMN, m, -1);
1832 	g_free(displaytext);
1833 
1834 	/* and store */
1835 	if (!m->is_temp) {
1836 		bmark_store(BFWIN(doc->bfwin), m);
1837 	}
1838 	DEBUG_MSG("bmark_add_backend, set show symbols to true and call for redraw\n");
1839 	bluefish_text_view_set_show_symbols_redraw(BLUEFISH_TEXT_VIEW(doc->view), TRUE);
1840 	return m;
1841 }
1842 
1843 /**
1844  * bmark_text_for_offset:
1845  *
1846  * will use offset if itoffset is NULL
1847  * will use itoffset if not NULL
1848  */
1849 static gchar *
bmark_text_for_offset(Tdocument * doc,GtkTextIter * itoffset,gint offset)1850 bmark_text_for_offset(Tdocument * doc, GtkTextIter * itoffset, gint offset)
1851 {
1852 	GtkTextIter it, eit, sit;
1853 	if (itoffset) {
1854 		it = *itoffset;
1855 	} else {
1856 		gtk_text_buffer_get_iter_at_offset(doc->buffer, &it, offset);
1857 	}
1858 	sit = eit = it;
1859 	gtk_text_iter_forward_to_line_end(&eit);
1860 	gtk_text_iter_forward_chars(&sit, BMARK_SHOW_NUM_TEXT_CHARS);
1861 	if (!gtk_text_iter_in_range(&sit, &it, &eit))
1862 		sit = eit;
1863 #ifdef DEBUG
1864 	{
1865 		gchar *tmp = gtk_text_iter_get_text(&it, &sit);
1866 		DEBUG_MSG("bmark_text_for_offset, text=%s\n", tmp);
1867 		g_free(tmp);
1868 	}
1869 #endif
1870 	return gtk_text_iter_get_text(&it, &sit);
1871 }
1872 
1873 /* this function will add a bookmark to the current document at current cursor / selection */
1874 static void
bmark_add_current_doc_backend(Tbfwin * bfwin,const gchar * name,gint offset,gboolean is_temp)1875 bmark_add_current_doc_backend(Tbfwin * bfwin, const gchar * name, gint offset, gboolean is_temp)
1876 {
1877 	GtkTextIter it, eit, sit;
1878 	Tbmark *m;
1879 	if (!bfwin->current_document)
1880 		return;
1881 	DEBUG_MSG("bmark_add_backend, adding bookmark at offset=%d for bfwin=%p\n", offset, bfwin);
1882 	/* create bookmark */
1883 	gtk_text_buffer_get_iter_at_offset(DOCUMENT(bfwin->current_document)->buffer, &it, offset);
1884 	/* if there is a selection, and the offset is within the selection, we'll use it as text content */
1885 	if (gtk_text_buffer_get_selection_bounds(DOCUMENT(bfwin->current_document)->buffer, &sit, &eit)
1886 		&& gtk_text_iter_in_range(&it, &sit, &eit)) {
1887 		gchar *text = gtk_text_iter_get_text(&sit, &eit);
1888 		m = bmark_add_backend(DOCUMENT(bfwin->current_document), &sit, offset, name, text, is_temp);
1889 		g_free(text);
1890 
1891 	} else {
1892 		gchar *text;
1893 		text = bmark_text_for_offset(DOCUMENT(bfwin->current_document), &it, offset);
1894 		m = bmark_add_backend(DOCUMENT(bfwin->current_document), &it, offset, name, text, is_temp);
1895 		g_free(text);
1896 	}
1897 	if (bfwin->bmark) {			/* only if there is a left panel we should do this */
1898 		GtkTreePath *path;
1899 		path = gtk_tree_model_get_path(GTK_TREE_MODEL(BMARKDATA(bfwin->bmarkdata)->bookmarkstore), &m->iter);
1900 		gtk_tree_view_expand_to_path(bfwin->bmark, path);
1901 		gtk_tree_path_free(path);
1902 		gtk_widget_grab_focus(bfwin->current_document->view);
1903 	}
1904 }
1905 
1906 /*
1907 can we make this function faster? when adding bookmarks from a search this function uses
1908 a lot of time, perhaps that can be improved
1909 */
1910 static Tbmark *
bmark_get_bmark_at_iter(Tdocument * doc,GtkTextIter * iter,gint offset)1911 bmark_get_bmark_at_iter(Tdocument * doc, GtkTextIter * iter, gint offset)
1912 {
1913 	GtkTextIter sit, eit;
1914 	GtkTreeIter tmpiter;
1915 	gint linenum;
1916 	sit = *iter;
1917 	linenum = gtk_text_iter_get_line(&sit);
1918 	eit = sit;
1919 	gtk_text_iter_set_line_offset(&sit, 0);
1920 	gtk_text_iter_forward_to_line_end(&eit);
1921 	/* check for existing bookmark in this place */
1922 	if (DOCUMENT(doc)->bmark_parent) {
1923 		GtkTextIter testit;
1924 		Tbmark *m, *m2;
1925 		/* the next function is probably the slowest since it jumps through the listmodel
1926 		   to find the right bookmark */
1927 		m = bmark_find_bookmark_before_offset(BFWIN(doc->bfwin), offset, doc->bmark_parent);
1928 		if (m == NULL) {
1929 			DEBUG_MSG("bmark_get_bmark_at_iter, m=NULL, get first child\n");
1930 			if (gtk_tree_model_iter_children
1931 				(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &tmpiter,
1932 				 doc->bmark_parent)) {
1933 				gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore),
1934 								   &tmpiter, PTR_COLUMN, &m2, -1);
1935 				gtk_text_buffer_get_iter_at_mark(doc->buffer, &testit, m2->mark);
1936 				if (gtk_text_iter_get_line(&testit) == linenum) {
1937 					return m2;
1938 				}
1939 			}
1940 		} else {
1941 			gtk_text_buffer_get_iter_at_mark(doc->buffer, &testit, m->mark);
1942 			DEBUG_MSG("bmark_get_bmark_at_iter, m=%p, has linenum=%d\n", m, gtk_text_iter_get_line(&testit));
1943 			if (gtk_text_iter_get_line(&testit) == linenum) {
1944 				return m;
1945 			}
1946 			tmpiter = m->iter;
1947 			if (gtk_tree_model_iter_next
1948 				(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore), &tmpiter)) {
1949 				gtk_tree_model_get(GTK_TREE_MODEL(BMARKDATA(BFWIN(doc->bfwin)->bmarkdata)->bookmarkstore),
1950 								   &tmpiter, PTR_COLUMN, &m2, -1);
1951 				gtk_text_buffer_get_iter_at_mark(doc->buffer, &testit, m2->mark);
1952 				if (gtk_text_iter_get_line(&testit) == linenum) {
1953 					return m2;
1954 				}
1955 			}
1956 		}
1957 		DEBUG_MSG("bmark_get_bmark_at_iter, nothing found at this line, return NULL\n");
1958 		return NULL;
1959 
1960 	}
1961 	DEBUG_MSG("bmark_get_bmark_at_iter, no existing bookmark found, return NULL\n");
1962 	return NULL;
1963 }
1964 
1965 static Tbmark *
bmark_get_bmark_at_line(Tdocument * doc,gint line)1966 bmark_get_bmark_at_line(Tdocument * doc, gint line)
1967 {
1968 	GtkTextIter iter;
1969 	gtk_text_buffer_get_iter_at_line(doc->buffer, &iter, line);
1970 	return bmark_get_bmark_at_iter(doc, &iter, gtk_text_iter_get_offset(&iter));
1971 }
1972 
1973 static Tbmark *
bmark_get_bmark_at_offset(Tdocument * doc,gint offset)1974 bmark_get_bmark_at_offset(Tdocument * doc, gint offset)
1975 {
1976 	GtkTextIter iter;
1977 	gtk_text_buffer_get_iter_at_offset(doc->buffer, &iter, offset);
1978 	return bmark_get_bmark_at_iter(doc, &iter, offset);
1979 }
1980 
1981 /**
1982  * bmark_add_extern
1983  * @doc: a #Tdocument* with the document
1984  * @offset: the character position where to set the bookmark
1985  * @name: a name to set for the bookmark, or NULL for no name
1986  * @text: the text for the bookmark, or NULL to have it set automatically
1987  *
1988  * Code in bluefish that want to set a bookmark, not related to
1989  * the cursor location or a mouse position should use
1990  * this function.
1991  */
1992 void
bmark_add_extern(Tdocument * doc,gint offset,const gchar * name,const gchar * text,gboolean is_temp)1993 bmark_add_extern(Tdocument * doc, gint offset, const gchar * name, const gchar * text, gboolean is_temp)
1994 {
1995 	if (!doc)
1996 		return;
1997 	DEBUG_MSG("adding bookmark at offset %d with name %s\n", offset, name);	/* dummy */
1998 	if (!bmark_get_bmark_at_offset(doc, offset)) {
1999 		if (text) {
2000 			bmark_add_backend(doc, NULL, offset, (name) ? name : "", text, is_temp);
2001 		} else {
2002 			gchar *tmp = bmark_text_for_offset(doc, NULL, offset);
2003 			bmark_add_backend(doc, NULL, offset, (name) ? name : "", tmp, is_temp);
2004 			g_free(tmp);
2005 		}
2006 	}
2007 }
2008 
2009 void
bmark_toggle(Tdocument * doc,gint offset,const gchar * name,const gchar * text)2010 bmark_toggle(Tdocument * doc, gint offset, const gchar * name, const gchar * text)
2011 {
2012 	Tbmark *bmark;
2013 	if (!doc)
2014 		return;
2015 	bmark = bmark_get_bmark_at_offset(doc, offset);
2016 	if (bmark) {
2017 		bmark_check_remove(BFWIN(doc->bfwin), bmark);	/* check  if we should remove a filename too */
2018 		bmark_unstore(BFWIN(doc->bfwin), bmark);
2019 		bmark_free(bmark);
2020 	} else {
2021 		bmark_add_extern(doc, offset, name, text, !main_v->globses.bookmarks_default_store);
2022 	}
2023 }
2024 
2025 void
bmark_toggle_at_cursor(Tbfwin * bfwin)2026 bmark_toggle_at_cursor(Tbfwin * bfwin)
2027 {
2028 	GtkTextIter it, it2;
2029 	gint offset;
2030 	Tbmark *bmark;
2031 	/* if the left panel is disabled, we simply should add the bookmark to the list, and do nothing else */
2032 	gtk_text_buffer_get_iter_at_mark(DOCUMENT(bfwin->current_document)->buffer, &it,
2033 									 gtk_text_buffer_get_insert(DOCUMENT(bfwin->current_document)->buffer));
2034 	gtk_text_buffer_get_iter_at_mark(DOCUMENT(bfwin->current_document)->buffer, &it2,
2035 									 gtk_text_buffer_get_selection_bound(DOCUMENT
2036 																		 (bfwin->current_document)->buffer));
2037 	gtk_text_iter_order(&it, &it2);
2038 	offset = gtk_text_iter_get_offset(&it);
2039 	/* check for existing bookmark in this place */
2040 	bmark = bmark_get_bmark_at_offset(DOCUMENT(bfwin->current_document), offset);
2041 	if (bmark) {
2042 		bmark_check_remove(bfwin, bmark);	/* check  if we should remove a filename too */
2043 		bmark_unstore(bfwin, bmark);
2044 		bmark_free(bmark);
2045 	} else {
2046 		bmark_add_current_doc_backend(bfwin, "", offset, !main_v->globses.bookmarks_default_store);
2047 	}
2048 }
2049 
2050 gboolean
bmark_have_bookmark_at_stored_bevent(Tdocument * doc)2051 bmark_have_bookmark_at_stored_bevent(Tdocument * doc)
2052 {
2053 	if (main_v->bevent_doc == doc) {
2054 		return (bmark_get_bmark_at_offset(doc, main_v->bevent_charoffset) != NULL);
2055 	}
2056 	return FALSE;
2057 }
2058 
2059 gchar *
bmark_get_tooltip_for_line(Tdocument * doc,gint line)2060 bmark_get_tooltip_for_line(Tdocument * doc, gint line)
2061 {
2062 	Tbmark *bmark;
2063 	bmark = bmark_get_bmark_at_line(doc, line);
2064 	if (!bmark || !bmark->text)
2065 		return NULL;
2066 	if (bmark->text && bmark->name)
2067 		return g_markup_printf_escaped("%s %s", bmark->name, bmark->text);
2068 	if (bmark->name)
2069 		return g_markup_escape_text(bmark->name, -1);
2070 	return g_markup_escape_text(bmark->text, -1);
2071 }
2072 
2073 
2074 /*gchar *
2075 bmark_get_tooltip_for_line(Tdocument *doc, gint line)
2076 {
2077 	Tbmark *bmark;
2078 	bmark = bmark_get_bmark_at_line(doc, line);
2079 	if (!bmark || !bmark->text)
2080 		return NULL;
2081 	if (bmark->text && bmark->name)
2082 		return g_strconcat(bmark->name," ", bmark->text, NULL);
2083 	if (bmark->name)
2084 		return g_strdup(bmark->name);
2085 	return g_strdup(bmark->text);
2086 }
2087 */
2088 void
bmark_del_at_bevent(Tdocument * doc)2089 bmark_del_at_bevent(Tdocument * doc)
2090 {
2091 	if (main_v->bevent_doc == doc) {
2092 		Tbmark *b = bmark_get_bmark_at_offset(doc, main_v->bevent_charoffset);
2093 		if (b) {
2094 			DEBUG_MSG("bmark_del_at_bevent, deleting bookmark %p\n", b);
2095 			bmark_check_remove(BFWIN(doc->bfwin), b);	/* check  if we should remove a filename too */
2096 			bmark_unstore(BFWIN(doc->bfwin), b);
2097 			bmark_free(b);
2098 		}
2099 	}
2100 }
2101 
2102 void
bmark_add_at_bevent(Tdocument * doc)2103 bmark_add_at_bevent(Tdocument * doc)
2104 {
2105 	/* check for unnamed document */
2106 	if (main_v->bevent_doc == doc) {
2107 		gint offset = main_v->bevent_charoffset;
2108 		/* we have the location */
2109 		bmark_add_current_doc_backend(doc->bfwin, "", offset, !main_v->globses.bookmarks_default_store);
2110 	}
2111 }
2112 
2113 /* not used yet
2114 void bmark_del_for_filename(Tbfwin *bfwin, gchar *filename) {
2115 	GtkTreeIter *parent = (GtkTreeIter *)g_hash_table_lookup(BMARKDATA(bfwin->bmarkdata)->bmarkfiles,filename);
2116 	if (parent) {
2117 		bmark_del_children_backend(bfwin, parent);
2118 	}
2119 }
2120 */
2121 
2122 void
bmark_cleanup(Tbfwin * bfwin)2123 bmark_cleanup(Tbfwin * bfwin)
2124 {
2125 	DEBUG_MSG("bmark_cleanup, cleanup for bfwin=%p\n", bfwin);
2126 	bfwin->bmark = NULL;
2127 	bfwin->bmarkfilter = NULL;
2128 	g_free(bfwin->bmark_search_prefix);
2129 	bfwin->bmark_search_prefix = NULL;
2130 #ifdef BMARKREF
2131 	g_print("bmark_cleanup, itercount=%d\n", bmarkref.itercount);
2132 #endif
2133 }
2134