1 /* Bluefish HTML Editor
2  * bftextview2.c
3  *
4  * Copyright (C) 2008-2017 Olivier Sessink
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 /* indented with indent -ts4 -kr -l110 */
20 /* for the design docs see bftextview2.h */
21 
22 #include <math.h>				/* log10() */
23 #include <string.h>				/* strlen() */
24 
25 #include "bftextview2.h"
26 #include "bftextview2_private.h"
27 
28 #include "bluefish.h"
29 #include "bf_lib.h"
30 #include "bookmark.h"
31 #include "document.h"
32 #include "undo_redo.h"
33 #include "doc_text_tools.h"
34 #include "bfwin.h"
35 #include "bftextview2_scanner.h"
36 #include "bftextview2_patcompile.h"
37 #include "bftextview2_autocomp.h"
38 #include "bftextview2_langmgr.h"
39 #ifdef HAVE_LIBENCHANT
40 #include "bftextview2_spell.h"
41 #endif
42 #ifdef MARKREGION
43 #include "bftextview2_markregion.h"
44 #endif
45 
46 /*#undef DEBUG_MSG
47 #define DEBUG_MSG g_print*/
48 /*#undef DBG_SIGNALS
49 #define DBG_SIGNALS g_print*/
50 /*#undef DBG_SCANCACHE
51 #define DBG_SCANCACHE g_print
52 #undef DBG_SCANNING
53 #define DBG_SCANNING g_print
54 #undef DBG_AUTOCOMP
55 #define DBG_AUTOCOMP g_print*/
56 
57 #define USER_IDLE_EVENT_INTERVAL 480	/* milliseconds */
58 
G_DEFINE_TYPE(BluefishTextView,bluefish_text_view,GTK_TYPE_TEXT_VIEW)59 G_DEFINE_TYPE(BluefishTextView, bluefish_text_view, GTK_TYPE_TEXT_VIEW)
60 #if GTK_CHECK_VERSION(3,0,0)
61 static GdkRGBA st_whitespace_color, st_cline_color, st_cursor_highlight_color, st_margin_fg_color,
62 	st_margin_bg_color;
63 #else
64 static GdkColor st_whitespace_color, st_cline_color, st_cursor_highlight_color, st_margin_fg_color,
65 	st_margin_bg_color;
66 #endif
67 
68 /****************************** utility functions ******************************/
69 
70 const gchar *bluefish_text_view_get_lang_name(BluefishTextView * btv)
71 {
72 	if (!btv)
73 		return NULL;
74 	if (!btv->bflang)
75 		return NULL;
76 	return btv->bflang->name;
77 }
78 
character_is_symbol(Tscantable * st,guint16 context,gunichar uc)79 gboolean character_is_symbol(Tscantable * st, guint16 context, gunichar uc)
80 {
81 	if (uc > 127)
82 		return FALSE;
83 	if (!st) {
84 		return FALSE;
85 	}
86 	if (context > st->contexts->len) {
87 		return FALSE;
88 	}
89 	return (g_array_index((GArray *) g_array_index(st->contexts, Tcontext, context).table, Ttablerow, 1).row
90 			[uc] != 1);
91 }
92 
is_symbol(BluefishTextView * btv,gint contextnum,gunichar uc)93 static inline gboolean is_symbol(BluefishTextView * btv, gint contextnum, gunichar uc)
94 {
95 	if (G_UNLIKELY(uc > 127))
96 		return FALSE;
97 	return character_is_symbol(((Tscantable *) btv->bflang->st), contextnum, uc);
98 }
99 
bf_get_identifier_at_iter(BluefishTextView * btv,GtkTextIter * iter,gint * contextnum)100 gchar *bf_get_identifier_at_iter(BluefishTextView * btv, GtkTextIter * iter, gint * contextnum)
101 {
102 	GQueue *contextstack;
103 	GtkTextIter so, eo;
104 	g_assert(btv == btv->master);
105 	so = eo = *iter;
106 	if (!btv->bflang || !btv->bflang->st) {
107 		while (gtk_text_iter_backward_char(&so) && !g_unichar_isspace(gtk_text_iter_get_char(&so))) {
108 		};
109 		gtk_text_iter_forward_char(&so);
110 		while (!g_unichar_isspace(gtk_text_iter_get_char(&eo)) && gtk_text_iter_forward_char(&eo)) {
111 		};
112 
113 		return gtk_text_buffer_get_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv)), &so, &eo, TRUE);
114 	}
115 	contextstack = get_contextstack_at_position(btv, iter);
116 	if (g_queue_get_length(contextstack) > 0)
117 		*contextnum = GPOINTER_TO_INT(g_queue_peek_head(contextstack));
118 	else
119 		*contextnum = 1;
120 
121 	while (gtk_text_iter_backward_char(&so) && !is_symbol(btv, *contextnum, gtk_text_iter_get_char(&so))) {
122 		/*g_print("evaluating char %c\n",gtk_text_iter_get_char(&so)); */
123 	};
124 	gtk_text_iter_forward_char(&so);
125 	while (gtk_text_iter_forward_char(&eo) && !is_symbol(btv, *contextnum, gtk_text_iter_get_char(&eo))) {
126 		/*g_print("evaluating char %c\n",gtk_text_iter_get_char(&so)); */
127 	};
128 	return gtk_text_buffer_get_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv)), &so, &eo, TRUE);
129 }
130 
131 
132 gboolean
bf_text_iter_line_start_of_text(BluefishTextView * btv,GtkTextIter * iter,GtkTextIter * realstart,gboolean use_wrapped)133 bf_text_iter_line_start_of_text(BluefishTextView * btv, GtkTextIter * iter, GtkTextIter * realstart,
134 								gboolean use_wrapped)
135 {
136 	gboolean ret;
137 	/*g_print("bf_text_iter_line_start_of_text, started at offset %d\n",gtk_text_iter_get_offset(iter)); */
138 	if (use_wrapped && gtk_text_view_get_wrap_mode(GTK_TEXT_VIEW(btv)) != GTK_WRAP_NONE) {
139 		ret = gtk_text_view_backward_display_line_start(GTK_TEXT_VIEW(btv), iter);
140 		if (!ret) {
141 			/*g_print("bf_text_iter_line_start_of_text, failed to move iter to display_line_start, return FALSE\n"); */
142 			return FALSE;
143 		}
144 		/*g_print("bf_text_iter_line_start_of_text, line offset=%d\n",gtk_text_iter_get_line_offset(iter)); */
145 		*realstart = *iter;
146 		if (gtk_text_iter_get_line_offset(iter) > 0) {
147 			/* we have wrapped text, and we found the start of a wrapped part, so this is not the start of the real line */
148 			return TRUE;
149 		}
150 	} else {
151 		/*g_print("bf_text_iter_line_start_of_text, no wrap, or no-want-wrap, set line offset to 0\n"); */
152 		gtk_text_iter_set_line_offset(iter, 0);
153 		*realstart = *iter;
154 	}
155 	/* do the magic so we can toggle between the line start and the start of the text */
156 	ret = TRUE;
157 	while (ret && g_unichar_isspace(gtk_text_iter_get_char(iter))) {
158 		if (gtk_text_iter_ends_line(iter))
159 			return FALSE;
160 		ret = gtk_text_iter_forward_char(iter);
161 	}
162 	DEBUG_MSG("bf_text_iter_line_start_of_text, end of function, ret=%d, line offset=%d, offset=%d\n", ret,
163 			  gtk_text_iter_get_line_offset(iter), gtk_text_iter_get_offset(iter));
164 	return ret;
165 }
166 
167 /* if BluefishTextView *btv is NULL we do not check for the wrap mode, and the function will always
168 return the real line, not the wrapped part  */
169 gboolean
bf_text_iter_line_end_of_text(BluefishTextView * btv,GtkTextIter * iter,GtkTextIter * realend,gboolean use_wrapped)170 bf_text_iter_line_end_of_text(BluefishTextView * btv, GtkTextIter * iter, GtkTextIter * realend,
171 							  gboolean use_wrapped)
172 {
173 	gboolean ret;
174 	if (use_wrapped && gtk_text_view_get_wrap_mode(GTK_TEXT_VIEW(btv)) != GTK_WRAP_NONE) {
175 		ret = gtk_text_view_forward_display_line_end(GTK_TEXT_VIEW(btv), iter);
176 		if (!ret) {
177 			/*g_print("bf_text_iter_line_end_of_text, failed to move iter to display_line_end, return FALSE\n"); */
178 			return FALSE;
179 		}
180 		/*g_print("bf_text_iter_line_end_of_text, line offset=%d\n",gtk_text_iter_get_line_offset(iter)); */
181 		*realend = *iter;
182 		if (!gtk_text_iter_ends_line(iter)) {
183 			/* we have wrapped text, and we found the end of a wrapped part, so this is not the end of the real line */
184 			return TRUE;
185 		}
186 	}
187 
188 	if (!gtk_text_iter_ends_line(iter)) {
189 		gtk_text_iter_forward_to_line_end(iter);
190 	}
191 	if (gtk_text_iter_starts_line(iter)) {
192 		return FALSE;
193 	}
194 	*realend = *iter;
195 	if (gtk_text_iter_is_end(iter))
196 		gtk_text_iter_backward_char(iter);
197 	ret = TRUE;
198 	while (ret && g_unichar_isspace(gtk_text_iter_get_char(iter))) {
199 		if (gtk_text_iter_starts_line(iter))
200 			return FALSE;
201 		ret = gtk_text_iter_backward_char(iter);
202 	}
203 	if (ret && !gtk_text_iter_ends_line(iter) && !g_unichar_isspace(gtk_text_iter_get_char(iter)))
204 		gtk_text_iter_forward_char(iter);
205 	return ret;
206 }
207 
208 /*************************** end of utility functions **************************/
bftextview2_user_idle_timer(gpointer data)209 static gboolean bftextview2_user_idle_timer(gpointer data)
210 {
211 	BluefishTextView *btv = data;
212 	guint elapsed = (guint) (1000.0 * g_timer_elapsed(btv->user_idle_timer, NULL));
213 	if ((elapsed + 20) >= USER_IDLE_EVENT_INTERVAL) {	/* avoid delaying again for less than 20 milliseconds */
214 		DBG_AUTOCOMP
215 			("bftextview2_user_idle_timer, user is > %d milliseconds idle, autocomp=%d, mode=%d, needs_autocomp=%d\n",
216 			 elapsed, BLUEFISH_TEXT_VIEW(btv->master)->auto_complete, main_v->props.autocomp_popup_mode,
217 			 btv->needs_autocomp);
218 		if (BLUEFISH_TEXT_VIEW(btv->master)->auto_complete && btv->needs_autocomp
219 			&& main_v->props.autocomp_popup_mode == 0) {
220 			autocomp_run(btv, FALSE);
221 			DBG_AUTOCOMP("bftextview2_user_idle_timer, after run, set needs_autocomp to FALSE\n");
222 			btv->needs_autocomp = FALSE;
223 		}
224 		btv->user_idle = 0;
225 		return FALSE;
226 	} else {
227 		guint nextcheck;
228 		nextcheck = USER_IDLE_EVENT_INTERVAL - elapsed;
229 		DBG_AUTOCOMP
230 			("bftextview2_user_idle_timer, not yet elapsed (%d milliseconds idle), rechecking in %d milliseconds\n",
231 			 elapsed, nextcheck);
232 		btv->user_idle = g_timeout_add(nextcheck, bftextview2_user_idle_timer, btv);
233 		return FALSE;
234 	}
235 }
236 
bftextview2_reset_user_idle_timer(BluefishTextView * btv)237 static void bftextview2_reset_user_idle_timer(BluefishTextView * btv)
238 {
239 	DBG_AUTOCOMP("bftextview2_reset_user_idle_timer: timer reset\n");
240 	g_timer_start(btv->user_idle_timer);	/* the timer is used both for delayed scanning AND for the delayed popup */
241 	if (btv->user_idle == 0 && main_v->props.autocomp_popup_mode == 0 /*&&btv->autocomp */ ) {
242 		btv->user_idle = g_timeout_add(USER_IDLE_EVENT_INTERVAL, bftextview2_user_idle_timer, btv);
243 		DBG_AUTOCOMP("started user_idle timeout with event source %d and timeout %d\n", btv->user_idle,
244 					 USER_IDLE_EVENT_INTERVAL);
245 	}
246 }
247 
248 static void bftextview2_set_margin_size(BluefishTextView * btv);
249 static gboolean bftextview2_scanner_idle(gpointer data);
250 
bftextview2_scanner_scan(BluefishTextView * btv,gboolean in_idle)251 static gboolean bftextview2_scanner_scan(BluefishTextView * btv, gboolean in_idle)
252 {
253 	if (!btv->bflang) {
254 		btv->scanner_idle = 0;
255 		btv->scanner_immediate = 0;
256 		btv->scanner_delayed = 0;
257 		DBG_MSG("bftextview2_scanner_scan, no bflang, return FALSE\n");
258 		return FALSE;
259 	}
260 	if (!btv->bflang->st
261 #ifdef HAVE_LIBENCHANT
262 		&& !btv->spell_check
263 #endif
264 		) {
265 		DBG_MSG("bftextview2_scanner_scan, no scantable or no spellcheck, return FALSE\n");
266 		btv->scanner_idle = 0;
267 		btv->scanner_immediate = 0;
268 		btv->scanner_delayed = 0;
269 		return FALSE;
270 	}
271 	DBG_DELAYSCANNING("bftextview2_scanner_scan, start scanning\n");
272 	{
273 		DBG_DELAYSCANNING
274 			("bftextview2_scanner_idle, running scanner idle function, scanner_idle=%d, scanner_immediate=%d\n",
275 			 btv->scanner_idle, btv->scanner_immediate);
276 		if (!(btv->enable_scanner && bftextview2_run_scanner(btv, NULL))
277 #ifdef HAVE_LIBENCHANT
278 			&& !bftextview2_run_spellcheck(btv)
279 #endif
280 			) {
281 			btv->scanner_idle = 0;
282 			btv->scanner_immediate = 0;
283 			DBG_DELAYSCANNING("bftextview2_scanner_idle, stopping scanner idle function\n");
284 			bftextview2_set_margin_size(btv);
285 			return FALSE;
286 		} else if (btv->scanner_immediate) {
287 			DBG_DELAYSCANNING
288 				("bftextview2_scanner_idle, stop immediate priority callback, start idle priority callback, priority=%d\n",
289 				 SCANNING_IDLE_AFTER_TIMEOUT_PRIORITY);
290 			btv->scanner_immediate = 0;
291 			btv->scanner_idle =
292 				g_idle_add_full(SCANNING_IDLE_AFTER_TIMEOUT_PRIORITY, bftextview2_scanner_idle, btv, NULL);
293 			DBG_DELAYSCANNING("bftextview2_scanner_idle, idle priority callback at %d\n", btv->scanner_idle);
294 			return FALSE;
295 		}
296 	}
297 	DBG_DELAYSCANNING("bftextview2_scanner_scan, end of function, return TRUE\n");
298 	return TRUE;				/* call me again */
299 }
300 
bftextview2_scanner_idle(gpointer data)301 static gboolean bftextview2_scanner_idle(gpointer data)
302 {
303 	BluefishTextView *btv = data;
304 #ifdef DEBUG_SIGNALS
305 	if (btv->scanner_immediate) {
306 		g_print("bftextview2_scanner_idle, immediate, priority=%d\n", SCANNING_IDLE_PRIORITY);
307 	} else {
308 		g_print("bftextview2_scanner_idle, idle, priority=%d\n", SCANNING_IDLE_AFTER_TIMEOUT_PRIORITY);
309 	}
310 #endif
311 	DBG_DELAYSCANNING("bftextview2_scanner_idle callback started\n");
312 	if (!btv->enable_scanner
313 #ifdef HAVE_LIBENCHANT
314 		&& !btv->spell_check
315 #endif
316 		) {
317 		btv->scanner_idle = 0;
318 		btv->scanner_immediate = 0;
319 		return FALSE;
320 	}
321 	return bftextview2_scanner_scan(btv, TRUE);
322 }
323 
bftextview2_schedule_scanning(BluefishTextView * btv)324 void bftextview2_schedule_scanning(BluefishTextView * btv)
325 {
326 	DBG_MSG
327 		("bftextview2_schedule_scanning, enable=%d, spell_check=%d, bflang=%p,scanner_idle=%d,scanner_immediate=%d\n",
328 		 BLUEFISH_TEXT_VIEW(btv->master)->enable_scanner,
329 #ifdef HAVE_LIBENCHANT
330 		 BLUEFISH_TEXT_VIEW(btv->master)->spell_check,
331 #else
332 		 0
333 #endif
334 		 BLUEFISH_TEXT_VIEW(btv->master)->bflang,
335 		 BLUEFISH_TEXT_VIEW(btv->master)->scanner_idle, BLUEFISH_TEXT_VIEW(btv->master)->scanner_immediate);
336 	if (BLUEFISH_TEXT_VIEW(btv->master)->scanner_idle) {
337 		DBG_MSG("bftextview2_schedule_scanning, remove scanning_idle callback\n");
338 		g_source_remove(BLUEFISH_TEXT_VIEW(btv->master)->scanner_idle);
339 		BLUEFISH_TEXT_VIEW(btv->master)->scanner_idle = 0;
340 	}
341 
342 	if ((BLUEFISH_TEXT_VIEW(btv->master)->enable_scanner
343 #ifdef HAVE_LIBENCHANT
344 		 || BLUEFISH_TEXT_VIEW(btv->master)->spell_check
345 #endif
346 		)
347 		&& BLUEFISH_TEXT_VIEW(btv->master)->bflang
348 		&& BLUEFISH_TEXT_VIEW(btv->master)->scanner_idle == 0
349 		&& BLUEFISH_TEXT_VIEW(btv->master)->scanner_immediate == 0) {
350 		DBG_DELAYSCANNING("scheduling scanning in idle function with priority %d\n", SCANNING_IDLE_PRIORITY);
351 		BLUEFISH_TEXT_VIEW(btv->master)->scanner_immediate =
352 			g_idle_add_full(SCANNING_IDLE_PRIORITY, bftextview2_scanner_idle, btv->master, NULL);
353 	}
354 	DBG_DELAYSCANNING("bftextview2_schedule_scanning, scanner_immediate=%d\n",
355 					  BLUEFISH_TEXT_VIEW(btv->master)->scanner_immediate);
356 }
357 
358 static void
bftextview2_get_iters_at_foundblock(GtkTextBuffer * buffer,Tfoundblock * fblock,GtkTextIter * it1,GtkTextIter * it2,GtkTextIter * it3,GtkTextIter * it4)359 bftextview2_get_iters_at_foundblock(GtkTextBuffer * buffer, Tfoundblock * fblock,
360 									GtkTextIter * it1, GtkTextIter * it2,
361 									GtkTextIter * it3, GtkTextIter * it4)
362 {
363 	gtk_text_buffer_get_iter_at_offset(buffer, it1, fblock->start1_o);
364 	gtk_text_buffer_get_iter_at_offset(buffer, it2, fblock->end1_o);
365 	gtk_text_buffer_get_iter_at_offset(buffer, it3, fblock->start2_o);
366 	gtk_text_buffer_get_iter_at_offset(buffer, it4, fblock->end2_o);
367 }
368 
369 /*
370 static inline Tfoundblock *
371 bftextview2_get_block_at_offset(BluefishTextView * btv, Tfound **found, guint offset)
372 {
373 	GSequenceIter *siter;
374 	Tfound *rfound;
375 	rfound = get_foundcache_at_offset(btv, offset, &siter);
376 	*found = rfound;
377 	while (rfound) {
378 		DBG_BLOCKMATCH("bftextview2_get_block_at_offset, found %p at offset %d with blockchange %d contextchange %d\n", rfound, rfound->charoffset_o, rfound->numblockchange, rfound->numcontextchange);
379 		if (IS_FOUNDMODE_BLOCKPUSH((rfound))
380 				&& ((rfound)->fblock->start1_o == offset || (rfound)->fblock->end1_o == offset)) {
381 			*found = rfound;
382 			return (rfound)->fblock;
383 		} else if ((rfound)->numblockchange < 0) {
384 			/ * TODO: if multiple blocks are popped, usually the last popped one if the one that matches thje end-of-block-tag
385 			so that block should be returned * /
386 			Tfoundblock *tmpfblock = pop_blocks((rfound)->numblockchange+1, (rfound)->fblock);
387 			DBG_BLOCKMATCH("bftextview2_get_block_at_offset, found->fblock=%p, tmpfblock=%p\n",rfound->fblock,tmpfblock);
388 			if (tmpfblock && (tmpfblock->start2_o == offset || tmpfblock->end2_o == offset)) {
389 				*found = rfound;
390 				return tmpfblock;
391 			}
392 		}
393 		if ((rfound)->charoffset_o > offset)
394 			break;
395 		*found = rfound;
396 		rfound = get_foundcache_next(btv, &siter);
397 	}
398 	return NULL;
399 }*/
400 
first_fully_defined_block(Tfoundblock * fblock)401 static Tfoundblock *first_fully_defined_block(Tfoundblock * fblock)
402 {
403 	while (fblock && fblock->start2_o == BF_OFFSET_UNDEFINED) {
404 		fblock = fblock->parentfblock;
405 	}
406 	return fblock;
407 }
408 
409 
410 /* if innerblock is TRUE we only return a block that we are between the matches, if innerblock is FALSE
411 we might return a block if we are within the startmatch or within the end match */
bftextview2_get_active_block_at_offset(BluefishTextView * btv,gboolean innerblock,guint offset)412 static Tfoundblock *bftextview2_get_active_block_at_offset(BluefishTextView * btv, gboolean innerblock,
413 														   guint offset)
414 {
415 	GSequenceIter *siter;
416 	Tfound *found1, *found2;
417 	Tfoundblock *fblock;
418 	found1 = get_foundcache_at_offset(btv, offset, &siter);
419 	if (!found1)
420 		return NULL;
421 	DEBUG_MSG("offset=%d, got found1 %p with offset %d and found1->fblock %p\n", offset, found1,
422 			  found1->charoffset_o, found1->fblock);
423 	if (innerblock) {
424 		if (!found1->fblock) {
425 			DEBUG_MSG
426 				("bftextview2_get_active_block_at_offset, found1 does not have an fblock, return NULL\n");
427 			return NULL;
428 		}
429 		fblock = first_fully_defined_block(found1->fblock);
430 		/* when innerblock is requested we have to check for the situation that we are in the middle of the end-of-block-match
431 		   because it means that we are outside the innerblock already, and thus we need the parent */
432 		if (fblock && found1->numblockchange < 0 && fblock->start2_o < offset) {
433 			DEBUG_MSG("bftextview2_get_active_block_at_offset, in end-of-block match, return parent\n");
434 			return fblock->parentfblock;
435 		}
436 	} else {
437 		/* when outerblock is requested we have to check if we are in the middle of a new start-of-block
438 		   which is stored in the next Tfound in the scancache */
439 		found2 = get_foundcache_next(btv, &siter);
440 		if (found2 && found2->numblockchange > 0 && found2->fblock->start1_o <= offset
441 			&& found2->fblock->start2_o != BF_OFFSET_UNDEFINED) {
442 			return found2->fblock;
443 		}
444 	}
445 	if (found1->numblockchange < 0) {
446 		fblock = first_fully_defined_block(found1->fblock);
447 		if (!fblock)
448 			return NULL;
449 		/* if outerblock is requested, we have to check for the situation that we are exactly at the end-of-block
450 		   in which case we don't have to pop the block yet */
451 		if (fblock->end2_o == offset) {
452 			return fblock;
453 		}
454 		DEBUG_MSG("return %d blocks popped from found1\n", found1->numblockchange);
455 		return pop_blocks(found1->numblockchange, found1->fblock);
456 	}
457 	DEBUG_MSG("return found1->fblock\n");
458 	return first_fully_defined_block(found1->fblock);
459 }
460 
461 gboolean
bluefish_text_view_get_active_block_boundaries(BluefishTextView * btv,guint location,gboolean innerblock,GtkTextIter * so,GtkTextIter * eo)462 bluefish_text_view_get_active_block_boundaries(BluefishTextView * btv, guint location, gboolean innerblock,
463 											   GtkTextIter * so, GtkTextIter * eo)
464 {
465 	GtkTextIter it1, it2;
466 	Tfoundblock *fblock = bftextview2_get_active_block_at_offset(btv, innerblock, location);
467 	if (!fblock)
468 		return FALSE;
469 	DEBUG_MSG("bluefish_text_view_get_active_block_boundaries, got block %p %d:%d-%d:%d\n",
470 			  fblock, fblock->start1_o, fblock->end1_o, fblock->start2_o, fblock->end2_o);
471 	fblock = first_fully_defined_block(fblock);
472 	if (!fblock)
473 		return FALSE;
474 	DEBUG_MSG("bluefish_text_view_get_active_block_boundaries, got fully defined block %p %d:%d-%d:%d\n",
475 			  fblock, fblock->start1_o, fblock->end1_o, fblock->start2_o, fblock->end2_o);
476 	if (innerblock)
477 		bftextview2_get_iters_at_foundblock(btv->buffer, fblock, &it1, so, eo, &it2);
478 	else
479 		bftextview2_get_iters_at_foundblock(btv->buffer, fblock, so, &it1, &it2, eo);
480 	return TRUE;
481 }
482 
483 gboolean
bluefish_text_view_get_active_identifier(BluefishTextView * btv,GtkTextIter * currentlocation,GtkTextIter * so,GtkTextIter * eo)484 bluefish_text_view_get_active_identifier(BluefishTextView * btv, GtkTextIter * currentlocation,
485 										 GtkTextIter * so, GtkTextIter * eo)
486 {
487 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
488 	guint patnum;
489 	gint contextnum;
490 	GtkTextIter mstart;
491 
492 	mstart = *currentlocation;
493 	gtk_text_iter_set_line_offset(&mstart, 0);
494 	patnum = scan_for_identifier_at_position(master, &mstart, currentlocation, &contextnum, so, eo);
495 	return (patnum != FALSE);
496 }
497 
blockstack_string(BluefishTextView * btv,Tfoundblock * fblock)498 static gchar *blockstack_string(BluefishTextView * btv, Tfoundblock * fblock)
499 {
500 	GString *tmp;
501 	Tfoundblock *parent;
502 	if (!fblock)
503 		return NULL;
504 
505 	parent = fblock;
506 	tmp = g_string_new("");
507 	while (parent) {
508 		if (parent->start2_o != BF_OFFSET_UNDEFINED) {
509 			if (parent != fblock)
510 				tmp = g_string_prepend(tmp, " ");
511 			if (g_array_index
512 				(BLUEFISH_TEXT_VIEW(btv->master)->bflang->st->matches, Tpattern,
513 				 parent->patternum).is_regex) {
514 				GtkTextIter it1, it2;
515 				gchar *tmp2;
516 				gtk_text_buffer_get_iter_at_offset(btv->buffer, &it1, parent->start1_o);
517 				gtk_text_buffer_get_iter_at_offset(btv->buffer, &it2, parent->end1_o);
518 				tmp2 = gtk_text_buffer_get_text(btv->buffer, &it1, &it2, TRUE);
519 				tmp = g_string_prepend(tmp, tmp2);
520 				g_free(tmp2);
521 			} else {
522 				tmp =
523 					g_string_prepend(tmp,
524 									 g_array_index(BLUEFISH_TEXT_VIEW(btv->master)->bflang->st->matches,
525 												   Tpattern, parent->patternum).pattern);
526 			}
527 		}
528 		parent = parent->parentfblock;
529 	}
530 	return g_string_free(tmp, FALSE);
531 }
532 
533 /* because we don't want to expose the Tfoundblock to the external API we return gpointer
534 external functions should treat this as a boolean: NULL means there is no block, a pointer means there is a block
535 */
536 gpointer
bftextview2_get_block_at_boundary_location(BluefishTextView * btv,guint offset,GtkTextIter * it1,GtkTextIter * it2,GtkTextIter * it3,GtkTextIter * it4)537 bftextview2_get_block_at_boundary_location(BluefishTextView * btv, guint offset, GtkTextIter * it1,
538 										   GtkTextIter * it2, GtkTextIter * it3, GtkTextIter * it4)
539 {
540 	Tfoundblock *fblock;
541 
542 	fblock = bftextview2_get_active_block_at_offset(btv->master, FALSE, offset);
543 	if (fblock)
544 		fblock = first_fully_defined_block(fblock);
545 	if (fblock) {
546 		if (fblock->start2_o != BF_OFFSET_UNDEFINED
547 			&& (fblock->start1_o == offset || fblock->end1_o == offset || fblock->start2_o == offset
548 				|| fblock->end2_o == offset)) {
549 			bftextview2_get_iters_at_foundblock(btv->buffer, fblock, it1, it2, it3, it4);
550 			return fblock;
551 		}
552 	}
553 	return NULL;
554 }
555 
mark_set_idle_lcb(gpointer widget)556 static gboolean mark_set_idle_lcb(gpointer widget)
557 {
558 	BluefishTextView *btv = widget;
559 	BluefishTextView *master = btv->master;
560 	GtkTextIter it1, it2, it3, it4, location;
561 	Tfoundblock *fblock;
562 	gchar *tmpstr = NULL;
563 	guint offset;
564 
565 	gtk_text_buffer_get_iter_at_mark(btv->buffer, &location, gtk_text_buffer_get_insert(btv->buffer));
566 	if (btv->showing_blockmatch || main_v->props.highlight_cursor) {
567 		gtk_text_buffer_get_bounds(btv->buffer, &it1, &it2);
568 		gtk_text_buffer_remove_tag(btv->buffer, master->blockmatch, &it1, &it2);
569 		btv->showing_blockmatch = FALSE;
570 	}
571 
572 	offset = gtk_text_iter_get_offset(&location);
573 	fblock = (Tfoundblock *) bftextview2_get_block_at_boundary_location(btv, offset, &it1, &it2, &it3, &it4);
574 	/*fblock = bftextview2_get_active_block_at_offset(master, FALSE, offset);
575 	   DBG_BLOCKMATCH("mark_set_idle_lcb, got fblock %p\n", fblock);
576 	   DBG_SIGNALS("mark_set_idle_lcb, 'insert' set at %d\n", gtk_text_iter_get_offset(&location));
577 	   if (fblock)
578 	   fblock = first_fully_defined_block(fblock);
579 	   if (fblock) {
580 	   if (fblock->start1_o == offset || fblock->end1_o == offset || fblock->start2_o == offset
581 	   || fblock->end2_o == offset) {
582 	   GtkTextIter it3, it4;
583 
584 	   DBG_BLOCKMATCH("mark_set_idle_lcb, got fblock %p with offsets %d:%d %d:%d\n", fblock,
585 	   fblock->start1_o, fblock->end1_o, fblock->start2_o, fblock->end2_o);
586 	   if (fblock->start2_o != BF_OFFSET_UNDEFINED) {
587 	   bftextview2_get_iters_at_foundblock(btv->buffer, fblock, &it1, &it2, &it3, &it4);
588 	   DBG_MSG("mark_set_idle_lcb, found a block to highlight the start (%d:%d) and end (%d:%d)\n",
589 	   gtk_text_iter_get_offset(&it1), gtk_text_iter_get_offset(&it2),
590 	   gtk_text_iter_get_offset(&it3), gtk_text_iter_get_offset(&it4));
591 	   gtk_text_buffer_apply_tag(btv->buffer, master->blockmatch, &it1, &it2);
592 	   gtk_text_buffer_apply_tag(btv->buffer, master->blockmatch, &it3, &it4);
593 	   btv->showing_blockmatch = TRUE;
594 	   } else {
595 	   DBG_MSG("mark_set_idle_lcb, block has no end - no matching\n");
596 	   }
597 	   } */
598 	if (fblock) {
599 		gtk_text_buffer_apply_tag(btv->buffer, master->blockmatch, &it1, &it2);
600 		gtk_text_buffer_apply_tag(btv->buffer, master->blockmatch, &it3, &it4);
601 		btv->showing_blockmatch = TRUE;
602 		if (BFWIN(DOCUMENT(master->doc)->bfwin)->session->view_blockstack)
603 			tmpstr = blockstack_string(btv, fblock);
604 	}							/*else if (found && found->fblock && BFWIN(DOCUMENT(btv->doc)->bfwin)->session->view_blockstack) {
605 								   fblock = found->fblock;
606 								   if (found->numblockchange < 0) {
607 								   fblock = pop_blocks(found->numblockchange, fblock);
608 								   }
609 								   tmpstr = blockstack_string(btv, fblock);
610 								   } */
611 	if (tmpstr) {
612 		bfwin_statusbar_message(DOCUMENT(master->doc)->bfwin, tmpstr, 2);
613 		g_free(tmpstr);
614 	}
615 	btv->mark_set_idle = 0;
616 	return FALSE;
617 }
618 
619 /* this function slows down scrolling when you hold the cursor pressed, because
620 it is called for every cursor position change. This function is therefore
621 an ideal candidate for speed optimization */
622 static void
bftextview2_mark_set_lcb(GtkTextBuffer * buffer,GtkTextIter * location,GtkTextMark * mark,gpointer widget)623 bftextview2_mark_set_lcb(GtkTextBuffer * buffer, GtkTextIter * location, GtkTextMark * mark, gpointer widget)
624 {
625 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
626 	DBG_SIGNALS("bftextview2_mark_set_lcb, mark=%p (insert=%p,selection=%p), location=%d\n", mark,
627 				gtk_text_buffer_get_insert(buffer), gtk_text_buffer_get_selection_bound(buffer),
628 				gtk_text_iter_get_offset(location));
629 	if (mark && gtk_text_buffer_get_insert(buffer) == mark) {
630 
631 		if (btv->autocomp)
632 			autocomp_stop(btv);
633 
634 		if (BLUEFISH_TEXT_VIEW(btv->master)->show_mbhl) {
635 			btv->needs_blockmatch = TRUE;
636 			DBG_BLOCKMATCH("set needs_blockmatch to TRUE for widget %p (master=%p)\n", btv, btv->master);
637 		}
638 
639 		if (BLUEFISH_TEXT_VIEW(btv->master)->bflang && BLUEFISH_TEXT_VIEW(btv->master)->bflang->st) {
640 			if (btv->user_idle) {
641 				g_source_remove(btv->user_idle);
642 				btv->user_idle = 0;
643 			}
644 		}
645 	}
646 }
647 
calc_pixels_per_char(BluefishTextView * btv)648 static void calc_pixels_per_char(BluefishTextView * btv)
649 {
650 	PangoLayout *panlay;
651 	panlay = gtk_widget_create_pango_layout(GTK_WIDGET(btv), "");
652 	pango_layout_set_text(panlay, "W", -1);
653 	pango_layout_get_pixel_size(panlay, &btv->margin_pixels_per_char, NULL);
654 	g_object_unref(G_OBJECT(panlay));
655 }
656 
bftextview2_set_margin_size(BluefishTextView * btv)657 static void bftextview2_set_margin_size(BluefishTextView * btv)
658 {
659 	gint lines, count, newsize;
660 
661 	g_assert(btv == btv->master);
662 
663 	DBG_MSG("bftextview2_set_margin_size, called for %p\n", btv);
664 	if (BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_per_char == 0) {
665 		calc_pixels_per_char(btv->master);
666 	}
667 	if (BLUEFISH_TEXT_VIEW(btv->master)->show_line_numbers) {
668 		lines = gtk_text_buffer_get_line_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv)));
669 		if (lines >= 100)
670 			count = 1 + log10(lines);
671 		else
672 			count = 2;
673 		BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_chars =
674 			4 + count * BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_per_char;
675 	} else {
676 		BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_chars = 0;
677 	}
678 	if (BLUEFISH_TEXT_VIEW(btv->master)->show_blocks
679 		&& g_sequence_get_length(BLUEFISH_TEXT_VIEW(btv->master)->scancache.foundcaches) > 0) {
680 		BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_block = 12;
681 	} else {
682 		BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_block = 0;
683 	}
684 	if (BLUEFISH_TEXT_VIEW(btv->master)->showsymbols) {
685 		BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_symbol = 12;
686 	} else {
687 		BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_symbol = 0;
688 	}
689 	/*g_print("lines=%d,count=%d,pixels_per_char=%d\n",lines,count,btv->margin_pixels_per_char); */
690 	newsize = BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_chars
691 		+ BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_block
692 		+ BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_symbol;
693 	gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_LEFT, newsize);
694 	if (btv->slave)
695 		gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(btv->slave), GTK_TEXT_WINDOW_LEFT, newsize);
696 }
697 
char_in_allsymbols(BluefishTextView * btv,gunichar uc)698 static inline gboolean char_in_allsymbols(BluefishTextView * btv, gunichar uc)
699 {
700 	if (uc > 127)
701 		return FALSE;
702 	if (!btv->bflang)
703 		return FALSE;
704 	if (btv->bflang->st)
705 		return btv->bflang->st->allsymbols[uc];
706 	else
707 		return (uc == ' ' || uc == '\t' || uc == '\n');
708 	return FALSE;
709 }
710 
711 static void
bftextview2_insert_text_lcb(GtkTextBuffer * buffer,GtkTextIter * iter,gchar * string,gint stringlen,BluefishTextView * btv)712 bftextview2_insert_text_lcb(GtkTextBuffer * buffer, GtkTextIter * iter, gchar * string,
713 							gint stringlen, BluefishTextView * btv)
714 {
715 	DBG_SIGNALS("bftextview2_insert_text_lcb, btv=%p, master=%p, stringlen=%d\n", btv, btv->master,
716 				stringlen);
717 	gchar *wrongquote = "¨";
718 	guint charlen = g_utf8_strlen(string, stringlen);
719 	guint startpos = gtk_text_iter_get_offset(iter);
720 
721 	if (main_v->props.editor_replace_unicode_quotes && charlen == 1 && stringlen == 2
722 		&& string[0] == wrongquote[0] && string[1] == wrongquote[1]) {
723 		DBG_MSG("Replace unicode quote ¨ with ascii quote \".\n");
724 		gtk_text_buffer_insert(buffer, iter, "\"", 1);
725 		g_signal_stop_emission_by_name(buffer, "insert_text");
726 	}
727 
728 	if (btv == btv->master) {
729 		foundcache_update_offsets(BLUEFISH_TEXT_VIEW(btv->master), startpos, charlen);
730 	}
731 }
732 
733 static void
bftextview2_insert_text_after_lcb(GtkTextBuffer * buffer,GtkTextIter * iter,gchar * string,gint stringlen,BluefishTextView * btv)734 bftextview2_insert_text_after_lcb(GtkTextBuffer * buffer, GtkTextIter * iter, gchar * string,
735 								  gint stringlen, BluefishTextView * btv)
736 {
737 	GtkTextIter start, end;
738 	guint charlen = g_utf8_strlen(string, stringlen);
739 	guint startpos;
740 	DBG_SIGNALS("bftextview2_insert_text_after_lcb, btv=%p, master=%p, stringlen=%d, string=%s\n", btv,
741 				btv->master, stringlen, string);
742 	if (DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->in_paste_operation)
743 		btv->needs_autocomp = FALSE;
744 	if (BLUEFISH_TEXT_VIEW(btv->master)->enable_scanner && btv->needs_autocomp
745 		&& BLUEFISH_TEXT_VIEW(btv->master)->auto_complete && stringlen == 1
746 		&& (btv->autocomp || main_v->props.autocomp_popup_mode != 0)) {
747 		DBG_AUTOCOMP("bftextview2_insert_text_after_lcb: call autocomp_run\n");
748 		autocomp_run(btv, FALSE);
749 		DBG_AUTOCOMP("bftextview2_insert_text_after_lcb, set needs_autocomp to FALSE\n");
750 		btv->needs_autocomp = FALSE;
751 	}
752 
753 	bftextview2_reset_user_idle_timer(btv);
754 	bftextview2_set_margin_size(BLUEFISH_TEXT_VIEW(btv->master));
755 
756 	if (btv != btv->master)
757 		return;
758 
759 	if (!main_v->props.reduced_scan_triggers || stringlen > 1
760 		|| (stringlen == 1 && char_in_allsymbols(btv, string[0]))) {
761 		bftextview2_schedule_scanning(btv);
762 	}
763 	/* mark the text that is changed */
764 	end = start = *iter;
765 	gtk_text_iter_backward_chars(&start, charlen);
766 
767 	DBG_SIGNALS("bftextview2_insert_text_after_lcb: mark text from %d to %d with markregion\n",
768 				gtk_text_iter_get_offset(&start), gtk_text_iter_get_offset(iter));
769 	startpos = gtk_text_iter_get_offset(&start);
770 #ifdef MARKREGION
771 	markregion_insert(&btv->scanning, startpos, startpos + charlen);
772 	DBG_MARKREGION("bftextview2_insert_text_after_lcb, apply needscanning to %u:%u\n",
773 				   gtk_text_iter_get_offset(&start), gtk_text_iter_get_offset(iter));
774 #ifdef HAVE_LIBENCHANT
775 	markregion_insert(&btv->spellcheck, startpos, startpos + charlen);
776 #endif
777 #endif
778 #ifdef NEEDSCANNING
779 	gtk_text_buffer_apply_tag(buffer, btv->needscanning, &start, iter);
780 #ifdef HAVE_LIBENCHANT
781 	DBG_SPELL("bftextview2_insert_text_after_lcb, mark area from %d to %d with tag 'needspellcheck' %p\n",
782 			  gtk_text_iter_get_offset(&start), gtk_text_iter_get_offset(iter), btv->needspellcheck);
783 	gtk_text_buffer_apply_tag(buffer, btv->needspellcheck, &start, &end);
784 #endif							/*HAVE_LIBENCHANT */
785 #endif							/* NEEDSCANNING */
786 	btv->needremovetags = 0;
787 
788 #ifdef MARKREGION
789 #ifdef NEEDSCANNING
790 	compare_markregion_needscanning(btv);
791 #endif
792 #endif
793 }
794 
795 /*static void print_found(Tfound * found)
796 {
797 	DBG_MARGIN("got found %p for next position", found);
798 	if (found) {
799 		DBG_MARGIN(" with line %d and charoffset %d and %d blocks", found->line, found->charoffset,
800 				g_queue_get_length(found->blockstack));
801 	}
802 	DBG_MARGIN("\n");
803 }*/
804 
paint_margin_expand(BluefishTextView * btv,cairo_t * cr,gint w,gint height)805 static inline void paint_margin_expand(BluefishTextView * btv, cairo_t * cr, gint w, gint height)
806 {
807 #if GTK_CHECK_VERSION(3,0,0)
808 	GtkStyleContext *cntxt;
809 	GdkRGBA rgba;
810 
811 	cntxt = gtk_widget_get_style_context(GTK_WIDGET(btv));
812 
813 	gtk_style_context_get_background_color(cntxt, gtk_widget_get_state_flags(GTK_WIDGET(btv)), &rgba);
814 	gdk_cairo_set_source_rgba(cr, &rgba);
815 #else
816 	gdk_cairo_set_source_color(cr,
817 							   &gtk_widget_get_style(GTK_WIDGET(btv))->base[gtk_widget_get_state
818 																			(GTK_WIDGET(btv))]);
819 #endif
820 
821 	cairo_rectangle(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 2, w + (height / 2) - 3, 7, 7);
822 	cairo_fill(cr);
823 
824 #if GTK_CHECK_VERSION(3,0,0)
825 	gtk_style_context_get_color(cntxt, gtk_widget_get_state_flags(GTK_WIDGET(btv)), &rgba);
826 	gdk_cairo_set_source_rgba(cr, &rgba);
827 #else
828 	gdk_cairo_set_source_color(cr,
829 							   &gtk_widget_get_style(GTK_WIDGET(btv))->fg[gtk_widget_get_state
830 																		  (GTK_WIDGET(btv))]);
831 #endif
832 
833 	cairo_rectangle(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 1.5, w + (height / 2) - 3.5, 8,
834 					8);
835 	cairo_move_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + (height / 2) + 5);
836 	cairo_line_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + height + 0.5);
837 	cairo_move_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 3, w + (height / 2) + 0.5);
838 	cairo_line_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 8, w + (height / 2) + 0.5);
839 	cairo_stroke(cr);
840 }
841 
paint_margin_collapse(BluefishTextView * btv,cairo_t * cr,gint w,gint height)842 static inline void paint_margin_collapse(BluefishTextView * btv, cairo_t * cr, gint w, gint height)
843 {
844 #if GTK_CHECK_VERSION(3,0,0)
845 	GtkStyleContext *cntxt;
846 	GdkRGBA rgba;
847 
848 	cntxt = gtk_widget_get_style_context(GTK_WIDGET(btv));
849 
850 	gtk_style_context_get_background_color(cntxt, gtk_widget_get_state_flags(GTK_WIDGET(btv)), &rgba);
851 	gdk_cairo_set_source_rgba(cr, &rgba);
852 #else
853 	gdk_cairo_set_source_color(cr,
854 							   &gtk_widget_get_style(GTK_WIDGET(btv))->base[gtk_widget_get_state
855 																			(GTK_WIDGET(btv))]);
856 #endif
857 
858 	cairo_rectangle(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 2, w + (height / 2) - 3, 7, 7);
859 	cairo_fill(cr);
860 
861 #if GTK_CHECK_VERSION(3,0,0)
862 	gtk_style_context_get_color(cntxt, gtk_widget_get_state_flags(GTK_WIDGET(btv)), &rgba);
863 	gdk_cairo_set_source_rgba(cr, &rgba);
864 #else
865 	gdk_cairo_set_source_color(cr,
866 							   &gtk_widget_get_style(GTK_WIDGET(btv))->fg[gtk_widget_get_state
867 																		  (GTK_WIDGET(btv))]);
868 #endif
869 
870 	cairo_rectangle(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 1.5, w + (height / 2) - 3.5, 8,
871 					8);
872 	cairo_move_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + (height / 2) - 2);
873 	cairo_line_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + (height / 2) + 3);
874 	cairo_move_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + (height / 2) + 5);
875 	cairo_line_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + height + 0.5);
876 	cairo_move_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 3, w + (height / 2) + 0.5);
877 	cairo_line_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 8, w + (height / 2) + 0.5);
878 	cairo_stroke(cr);
879 }
880 
paint_margin_blockend(BluefishTextView * btv,cairo_t * cr,gint w,gint height)881 static inline void paint_margin_blockend(BluefishTextView * btv, cairo_t * cr, gint w, gint height)
882 {
883 	/*gdk_draw_line(GDK_DRAWABLE(event->window),GTK_WIDGET(btv)->style->fg_gc[gtk_widget_get_state(GTK_WIDGET(btv))],btv->margin_pixels_chars+btv->margin_pixels_symbol+5, w, btv->margin_pixels_chars+btv->margin_pixels_symbol+5, w + (height/2));
884 	   gdk_draw_line(GDK_DRAWABLE(event->window),GTK_WIDGET(btv)->style->fg_gc[gtk_widget_get_state(GTK_WIDGET(btv))],btv->margin_pixels_chars+btv->margin_pixels_symbol+5, w+(height/2), btv->margin_pixels_chars+btv->margin_pixels_symbol+8, w + (height/2)); */
885 	cairo_move_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w);
886 	cairo_line_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + (height / 2) + 0.5);
887 	cairo_move_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + (height / 2) + 0.5);
888 	cairo_line_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 8.5, w + (height / 2) + 0.5);
889 	cairo_stroke(cr);
890 }
891 
paint_margin_line(BluefishTextView * btv,cairo_t * cr,gint w,gint height)892 static inline void paint_margin_line(BluefishTextView * btv, cairo_t * cr, gint w, gint height)
893 {
894 /*	gdk_draw_line(GDK_DRAWABLE(event->window),GTK_WIDGET(btv)->style->fg_gc[gtk_widget_get_state(GTK_WIDGET(btv))],btv->margin_pixels_chars+btv->margin_pixels_symbol+5, w, btv->margin_pixels_chars+btv->margin_pixels_symbol+5, w + height);*/
895 	cairo_move_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w);
896 	cairo_line_to(cr, btv->margin_pixels_chars + btv->margin_pixels_symbol + 5.5, w + height);
897 	cairo_stroke(cr);
898 }
899 
paint_margin_symbol(BluefishTextView * btv,cairo_t * cr,gint w,gint height)900 static inline void paint_margin_symbol(BluefishTextView * btv, cairo_t * cr, gint w, gint height)
901 {
902 	cairo_rectangle(cr, btv->margin_pixels_chars + 2, w + (height / 2) - 4, 8, 8);
903 	cairo_fill(cr);
904 }
905 
get_num_foldable_blocks(Tfound * found)906 static gint get_num_foldable_blocks(Tfound * found)
907 {
908 	gint count = 0;
909 	Tfoundblock *tmpfblock = found->fblock;
910 	if (found->numblockchange < 0 && found->fblock->foldable)
911 		count = found->numblockchange;	/* don't count popped blocks */
912 	DBG_MARGIN("found->numblockchange=%d, initial count=%d\n", found->numblockchange, count);
913 	while (tmpfblock) {
914 		DBG_MARGIN("check block %p (%d:%d), foldable=%d, parent=%p\n", tmpfblock, tmpfblock->start1_o,
915 				   tmpfblock->end2_o, tmpfblock->foldable, tmpfblock->parentfblock);
916 		if (tmpfblock->foldable)
917 			count++;
918 		tmpfblock = (Tfoundblock *) tmpfblock->parentfblock;
919 	}
920 	return count;
921 }
922 
923 static inline void
paint_margin(BluefishTextView * btv,cairo_t * cr,GtkTextIter * startvisible,GtkTextIter * endvisible)924 paint_margin(BluefishTextView * btv, cairo_t * cr, GtkTextIter * startvisible, GtkTextIter * endvisible)
925 {
926 	Tfound *found = NULL;
927 	BluefishTextView *master = btv->master;
928 	GSequenceIter *siter = NULL;
929 	guint num_blocks;
930 	gint cursor_line = -1;
931 	GtkTextIter it;
932 	GtkTextTag *folded;
933 	gint i;
934 	PangoLayout *panlay;
935 	gpointer bmark;
936 	gint bmarkline = -1;
937 
938 #if GTK_CHECK_VERSION(3,0,0)
939 	GtkStyleContext *cntxt;
940 	GdkRGBA rgba;
941 #endif
942 
943 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv));
944 	DBG_MSG("paint_margin called for %p\n", btv);
945 
946 	cairo_set_line_width(cr, 1);
947 	if (main_v->props.use_system_colors) {
948 #if GTK_CHECK_VERSION(3,0,0)
949 		cntxt = gtk_widget_get_style_context(GTK_WIDGET(btv));
950 		gtk_style_context_get_color(cntxt, gtk_widget_get_state_flags(GTK_WIDGET(btv)), &rgba);
951 		gdk_cairo_set_source_rgba(cr, &rgba);
952 #else
953 		gdk_cairo_set_source_color(cr,
954 								   &gtk_widget_get_style(GTK_WIDGET(btv))->fg[gtk_widget_get_state
955 																			  (GTK_WIDGET(btv))]);
956 #endif
957 	} else {
958 #if GTK_CHECK_VERSION(3,0,0)
959 		gdk_cairo_set_source_rgba(cr, &st_margin_fg_color);
960 #else
961 		gdk_cairo_set_source_color(cr, &st_margin_fg_color);
962 #endif
963 	}
964 	if (master->show_line_numbers) {
965 		GtkTextIter cursorit;
966 		gtk_text_buffer_get_iter_at_mark(buffer, &cursorit, gtk_text_buffer_get_insert(buffer));
967 		cursor_line = gtk_text_iter_get_line(&cursorit);
968 	}
969 
970 	/* to see how many blocks are active here */
971 	if (G_UNLIKELY(gtk_text_iter_is_start(startvisible)
972 				   && (g_sequence_get_length(master->scancache.foundcaches) != 0))) {
973 		siter = g_sequence_get_begin_iter(master->scancache.foundcaches);
974 		if (!g_sequence_iter_is_end(siter)) {
975 			found = g_sequence_get(siter);
976 		}
977 		num_blocks = 0;
978 		DBG_MARGIN("EXPOSE: start at begin, set num_blocks %d, found=%p\n", num_blocks, found);
979 	} else {
980 		found = get_foundcache_at_offset(master, gtk_text_iter_get_offset(startvisible), &siter);
981 		if (found) {
982 			num_blocks = get_num_foldable_blocks(found);
983 			DBG_MARGIN("EXPOSE: got %d foldable blocks at found %p at offset %d\n", num_blocks, found,
984 					   found->charoffset_o);
985 		} else {
986 			DBG_MARGIN("EXPOSE: no found for position %d, siter=%p\n",
987 					   gtk_text_iter_get_offset(startvisible), siter);
988 			num_blocks = 0;
989 		}
990 	}
991 	/* in the case that the *first* found is relevant, we don't need
992 	   the 'next' found */
993 	if (!found || found->charoffset_o < gtk_text_iter_get_offset(startvisible)) {
994 		DBG_MARGIN("get next found..\n");
995 		if (siter)
996 			found = get_foundcache_next(master, &siter);
997 	}
998 	/*DBG_MARGIN("first found ");
999 	   print_found(found); */
1000 
1001 	it = *startvisible;
1002 	panlay = gtk_widget_create_pango_layout(GTK_WIDGET(btv), "x");
1003 
1004 	folded = gtk_text_tag_table_lookup(langmgr_get_tagtable(), "_folded_");
1005 	if (master->showsymbols) {
1006 		bmarkline = bmark_margin_get_initial_bookmark((Tdocument *) master->doc, startvisible, &bmark);
1007 	}
1008 
1009 	for (i = gtk_text_iter_get_line(startvisible); i <= gtk_text_iter_get_line(endvisible); i++) {
1010 		gint w, height;
1011 		gchar *string;
1012 
1013 		gtk_text_iter_set_line(&it, i);
1014 
1015 		if (G_UNLIKELY(gtk_text_iter_has_tag(&it, folded))) {
1016 			DBG_FOLD("line %d is hidden\n", i);
1017 			num_blocks = -1;	/* -1 means invalid */
1018 		} else {
1019 			gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(btv), &it, &w, &height);
1020 			gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_LEFT, 0, w, NULL, &w);
1021 
1022 			/* line numbers */
1023 			if (master->show_line_numbers) {
1024 				if (i == cursor_line)
1025 					string = g_strdup_printf("<b>%d</b>", 1 + i);
1026 				else
1027 					string = g_strdup_printf("%d", 1 + i);
1028 				pango_layout_set_markup(panlay, string, -1);
1029 				cairo_move_to(cr, 2, w);
1030 				pango_cairo_show_layout(cr, panlay);
1031 				g_free(string);
1032 			}
1033 			/* symbols */
1034 			if (master->showsymbols && bmarkline != -1) {
1035 				while (bmarkline != -1 && bmarkline < i) {
1036 					bmarkline = bmark_margin_get_next_bookmark((Tdocument *) master->doc, &bmark);
1037 				}
1038 				if (G_UNLIKELY(bmarkline == i)) {
1039 					paint_margin_symbol(master, cr, w, height);
1040 				}
1041 			}
1042 			/* block folding.
1043 			   - to find out if we need an expander/collapse, we need to see if there is a pushedblock on the
1044 			   blockstack which has 'foldable' for any foundcache that is on this line.
1045 			   - to find if we need an end-of-block we need to see if there is a poppedblock on this line
1046 			   which has 'foldable'
1047 			   - to find out if we need a line or nothing we need to know the number of expanded blocks on the stack
1048 			 */
1049 			if (master->show_blocks) {
1050 				GtkTextIter nextline;
1051 				Tfound *oldfound;
1052 				gint paint = (num_blocks > 0) ? 1 : 0;	/* 0=nothing, 1=line, 2=expand, 3=collapse, 4=blockend */
1053 				guint nextline_o, curline_o;
1054 				/*g_print("line %d, num_blocks=%d, default paint=%d\n",i,num_blocks,paint); */
1055 				curline_o = gtk_text_iter_get_offset(&it);
1056 				nextline = it;
1057 				if (!gtk_text_iter_ends_line(&nextline)) {
1058 					gtk_text_iter_forward_to_line_end(&nextline);
1059 				}
1060 				nextline_o = gtk_text_iter_get_offset(&nextline);
1061 				while (found) {
1062 					guint foundpos = found->charoffset_o;
1063 					if (IS_FOUNDMODE_BLOCKPUSH(found)) {
1064 						/* on a pushedblock we should look where the block match start, charoffset_o is the end of the
1065 						   match, so multiline patterns are drawn on the wrong line */
1066 						foundpos = found->fblock->start1_o;
1067 					}
1068 					/*g_print("search block for line %d, curline_o=%d, nextline_o=%d, foundpos=%d, num_blocks=%d\n",i,curline_o,nextline_o, foundpos, num_blocks); */
1069 					if (foundpos > nextline_o) {
1070 						break;
1071 					}
1072 
1073 					if (foundpos <= nextline_o && foundpos >= curline_o) {
1074 						/*g_print("line %d, looking at found at position %d which has numblockchange=%d\n",i,found->charoffset_o,found->numblockchange); */
1075 						if (IS_FOUNDMODE_BLOCKPUSH(found) && found->fblock->foldable) {
1076 							paint = found->fblock->folded ? 3 : 2;
1077 							num_blocks = get_num_foldable_blocks(found);
1078 							/*g_print("paint_margin, pushed block, folded=%d, so paint=%d\n",found->fblock->folded,paint); */
1079 							break;
1080 						} else if (IS_FOUNDMODE_BLOCKPOP(found) && found->fblock->foldable) {
1081 							guint new_num_blocks = get_num_foldable_blocks(found);
1082 							if (new_num_blocks < num_blocks)
1083 								paint = 4;
1084 							/*else
1085 							   paint=1; */
1086 							/*g_print("paint_margin, line %d, new_num_blocks=%d, num_blocks=%d so paint=%d\n",i,new_num_blocks,num_blocks, paint); */
1087 							num_blocks = new_num_blocks;
1088 							/*break; */
1089 						}
1090 					}
1091 					oldfound = found;
1092 					found = get_foundcache_next(master, &siter);
1093 					/* I'm not 100% sure about the !found ||  that I added to the next line.. */
1094 					if (num_blocks == -1 && (!found || found->charoffset_o >= curline_o)) {
1095 						num_blocks = get_num_foldable_blocks(oldfound);
1096 						/*g_print("re-set num_blocks to %d using found at %d, next found at %d\n", num_blocks, oldfound->charoffset_o, found->charoffset_o); */
1097 						paint = (num_blocks > 0) ? 1 : 0;
1098 					}
1099 				}
1100 				switch (paint) {
1101 				case 0:
1102 					break;
1103 				case 1:
1104 					paint_margin_line(master, cr, w, height);
1105 					break;
1106 				case 2:
1107 					paint_margin_expand(master, cr, w, height);
1108 					break;
1109 				case 3:
1110 					paint_margin_collapse(master, cr, w, height);
1111 					break;
1112 				case 4:
1113 					paint_margin_blockend(master, cr, w, height);
1114 					break;
1115 				}
1116 			}
1117 		}
1118 	}
1119 	g_object_unref(G_OBJECT(panlay));
1120 }
1121 
1122 /* whitespace macro. Possibly include: '/n', 8206-8207, maybe others */
1123 #define BTV_ISWS(c) ( \
1124   ((c) == '\r') || \
1125   ((c) == '\n') || \
1126   ((c) == '\t') || \
1127   ((c) == ' ') || \
1128   ((c) == 160) || \
1129   ((c) == 8239) || \
1130   ((c) == 12288) || \
1131   ((c) >= 8192 && (c) <= 8205) \
1132 )
1133 /*
1134 main_v->props.visible_ws_mode:
1135 0 = All
1136 1 = All except spaces
1137 2 = All trailing
1138 3 = All except non-trailing spaces
1139 */
1140 static inline void
paint_spaces(BluefishTextView * btv,cairo_t * cr,GtkTextIter * startvisible,GtkTextIter * endvisible)1141 paint_spaces(BluefishTextView * btv, cairo_t * cr, GtkTextIter * startvisible, GtkTextIter * endvisible)
1142 {
1143 	GtkTextIter iter;
1144 	gunichar uc;
1145 	gboolean trailing = FALSE;
1146 
1147 	cairo_set_line_width(cr, 1.0);
1148 #if GTK_CHECK_VERSION(3,0,0)
1149 	gdk_cairo_set_source_rgba(cr, &st_whitespace_color);
1150 #else
1151 	gdk_cairo_set_source_color(cr, &st_whitespace_color);
1152 #endif
1153 	iter = *endvisible;
1154 	if (!gtk_text_iter_ends_line(&iter))
1155 		gtk_text_iter_forward_to_line_end(&iter);
1156 
1157 	while (!gtk_text_iter_equal(&iter, startvisible)) {	/* equal is faster than compare */
1158 		GdkRectangle rect;
1159 		gint x, y;
1160 		uc = gtk_text_iter_get_char(&iter);
1161 		if (G_UNLIKELY(BTV_ISWS(uc))) {
1162 			gtk_text_view_get_iter_location(GTK_TEXT_VIEW(btv), &iter, &rect);
1163 			gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT, rect.x,
1164 												  rect.y + rect.height / 1.5, &x, &y);
1165 #if GTK_CHECK_VERSION(3, 0, 0)
1166 			x += (BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_chars +
1167 				  BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_block +
1168 				  BLUEFISH_TEXT_VIEW(btv->master)->margin_pixels_symbol);
1169 #endif
1170 			if ((uc == '\n' || uc == '\r') && main_v->props.visible_ws_mode != 2) {
1171 				gint fourtheight = rect.height / 4;
1172 				/* draw newline or carriage return */
1173 				cairo_move_to(cr, x + 0.5, y - 0.5 - fourtheight);
1174 				cairo_rel_line_to(cr, fourtheight, 0);
1175 				cairo_rel_line_to(cr, 0, 2 * fourtheight);
1176 			} else if (uc == '\t' && (trailing || main_v->props.visible_ws_mode != 2)) {
1177 				/* draw tab */
1178 				cairo_move_to(cr, x + 3.5, y - 2.5);
1179 				cairo_rel_line_to(cr, 0, 3);
1180 				cairo_rel_line_to(cr, rect.width - 6, 0);
1181 				cairo_rel_line_to(cr, 0, -3);
1182 			} else if ((uc == 160 || uc == 8239) && (trailing || main_v->props.visible_ws_mode != 2)) {
1183 				/* draw nbsp (8239= narrow-nbsp) */
1184 				cairo_move_to(cr, x + 1, y - 0.5);
1185 				cairo_rel_line_to(cr, rect.width - 2, 0);
1186 			} else if (main_v->props.visible_ws_mode == 0 || (main_v->props.visible_ws_mode != 1 && trailing)) {
1187 				/* draw space */
1188 				x += rect.width / 2;
1189 				cairo_move_to(cr, x, y);
1190 				cairo_arc(cr, x, y, 0.75, 0, 2 * M_PI);
1191 			}
1192 		} else if (G_UNLIKELY(uc != '\n' && uc != '\r')) {
1193 			trailing = FALSE;
1194 		}
1195 
1196 		if (G_UNLIKELY(gtk_text_iter_starts_line(&iter))) {
1197 			trailing = TRUE;
1198 		}
1199 		gtk_text_iter_backward_char(&iter);
1200 	}
1201 	cairo_stroke(cr);
1202 }
1203 
1204 #if GTK_CHECK_VERSION(3,14,0)
bluefish_text_view_draw_layer(GtkTextView * text_view,GtkTextViewLayer layer,cairo_t * cr)1205 static void bluefish_text_view_draw_layer(GtkTextView * text_view, GtkTextViewLayer layer, cairo_t * cr)
1206 {
1207 	cairo_save(cr);
1208 
1209 	if (layer == GTK_TEXT_VIEW_LAYER_BELOW) {
1210 		/* in gtk 3.20 GTK_TEXT_VIEW_LAYER_BELOW_TEXT is added which works in buffer coordinates.
1211 		   In gtk 3.14 GTK_TEXT_VIEW_LAYER_BELOW is added which works in viewport coordinates */
1212 		BluefishTextView *btv = BLUEFISH_TEXT_VIEW(text_view);
1213 		BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
1214 
1215 		if (gtk_widget_is_sensitive(GTK_WIDGET(btv))
1216 			&& (BFWIN(DOCUMENT(master->doc)->bfwin)->session->view_cline || main_v->props.highlight_cursor)) {
1217 			gint y_wincoord, x_wincoord;
1218 			GtkTextIter it;
1219 			GdkRectangle itrect;
1220 
1221 
1222 			DBG_SIGNALS
1223 				("bluefish_text_view_draw_layer_event, current line highlighting, code for gtk >= 3.14\n");
1224 			gtk_text_buffer_get_iter_at_mark(master->buffer, &it, gtk_text_buffer_get_insert(master->buffer));
1225 			gtk_text_view_get_iter_location(text_view, &it, &itrect);
1226 			gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_TEXT, itrect.x, itrect.y,
1227 												  &x_wincoord, &y_wincoord);
1228 			if (BFWIN(DOCUMENT(master->doc)->bfwin)->session->view_cline && !DOCUMENT(master->doc)->readonly) {
1229 				gdouble x1, y1, x2, y2;
1230 
1231 				cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1232 				gdk_cairo_set_source_rgba(cr, &st_cline_color);
1233 				DBG_SIGNALS
1234 					("bluefish_text_view_draw_event, GTK >= 3.14 draw current line at x=%d,y=%d,width=%d,height=%d\n",
1235 					 x1 + .5, itrect.y + .5, x2 - x1 - 1, itrect.height - 1);
1236 				cairo_rectangle(cr, x1 + .5, y_wincoord + .5, x2 - x1 - 1, itrect.height - 1);
1237 				cairo_fill(cr);
1238 			}
1239 			if (main_v->props.highlight_cursor) {
1240 				gint width;
1241 				width = itrect.width > 5 ? itrect.width : master->margin_pixels_per_char;
1242 				gdk_cairo_set_source_rgba(cr, &st_cursor_highlight_color);
1243 				/* use y instead of itrect.y, because y is already converted to window coords */
1244 				DBG_SIGNALS
1245 					("bluefish_text_view_draw_event, GTK >= 3.14 draw highlight_cursor block, draw at x=%d\n",
1246 					 itrect.x);
1247 				cairo_rectangle(cr, x_wincoord, y_wincoord, width, itrect.height);
1248 				cairo_fill(cr);
1249 			}
1250 		}
1251 	}
1252 
1253 	cairo_restore(cr);
1254 }
1255 #endif
1256 
1257 #if GTK_CHECK_VERSION(3,0,0)
bluefish_text_view_draw(GtkWidget * widget,cairo_t * cr)1258 static gboolean bluefish_text_view_draw(GtkWidget * widget, cairo_t * cr)
1259 {
1260 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
1261 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
1262 	gboolean event_handled = FALSE;
1263 	GdkWindow *wleft, *wtext;
1264 	GdkRectangle rect;
1265 	gint rect2y, rect2x;
1266 	GtkTextIter startvisible, endvisible;
1267 
1268 #if GTK_CHECK_VERSION(3,14,0)
1269 	if (GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->draw)
1270 		event_handled = GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->draw(widget, cr);
1271 #endif
1272 
1273 	wleft = gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_LEFT);
1274 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &rect);
1275 	gtk_text_view_get_line_at_y(GTK_TEXT_VIEW(widget), &startvisible, rect.y, NULL);
1276 	gtk_text_view_get_line_at_y(GTK_TEXT_VIEW(widget), &endvisible, rect.y + rect.height, NULL);
1277 	gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT, rect.x,
1278 										  rect.y, &rect2x, &rect2y);
1279 
1280 	if (wleft && gtk_cairo_should_draw_window(cr, wleft)) {
1281 	   /******** the painting in the MARGIN area of the widget *********/
1282 		DBG_SIGNALS("bluefish_text_view_draw_event, GTK_TEXT_WINDOW_LEFT\n");
1283 		paint_margin(btv, cr, &startvisible, &endvisible);
1284 		event_handled = TRUE;
1285 	}
1286 	wtext = gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT);
1287 	if (wtext && gtk_cairo_should_draw_window(cr, wtext)) {
1288 	   /******** the painting in the TEXT area of the widget ********/
1289 #if !GTK_CHECK_VERSION(3,14,0)
1290 		if (gtk_widget_is_sensitive(GTK_WIDGET(btv))
1291 			&& (BFWIN(DOCUMENT(master->doc)->bfwin)->session->view_cline || main_v->props.highlight_cursor)) {
1292 			gint y2, x2;
1293 			GtkTextIter it;
1294 			GdkRectangle itrect;
1295 			DBG_SIGNALS("bluefish_text_view_draw_event, gtk < 3.14 current line highlighting\n");
1296 			gtk_text_buffer_get_iter_at_mark(master->buffer, &it, gtk_text_buffer_get_insert(master->buffer));
1297 			gtk_text_view_get_iter_location((GtkTextView *) widget, &it, &itrect);
1298 			gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT,
1299 												  itrect.x, itrect.y, &x2, &y2);
1300 			if (BFWIN(DOCUMENT(master->doc)->bfwin)->session->view_cline && !DOCUMENT(master->doc)->readonly) {
1301 				/*g_print("cline highlight, got itrect.y=%d, y2=%d, itrect.x=%d, x2=%d, itrect.height=%d, itrect.width=%d\n",itrect.y,y2,itrect.x, x2, itrect.height, itrect.width); */
1302 				gdk_cairo_set_source_rgba(cr, &st_cline_color);
1303 				cairo_rectangle(cr,
1304 								(gfloat) (master->margin_pixels_chars + master->margin_pixels_block +
1305 										  master->margin_pixels_symbol), (gfloat) y2,
1306 								(gfloat) gtk_widget_get_allocated_width(widget), (gfloat) itrect.height);
1307 				cairo_fill(cr);
1308 			}
1309 			if (main_v->props.highlight_cursor) {
1310 				gint width = itrect.width > 5 ? itrect.width : master->margin_pixels_per_char;
1311 				gdk_cairo_set_source_rgba(cr, &st_cursor_highlight_color);
1312 				DBG_SIGNALS("bluefish_text_view_draw_event, GTK < 3.14 draw highlight_cursor block\n");
1313 				cairo_rectangle(cr,
1314 								(gfloat) x2 - width + master->margin_pixels_chars +
1315 								master->margin_pixels_block + master->margin_pixels_symbol, (gfloat) y2,
1316 								(gfloat) (width * 2), (gfloat) itrect.height);
1317 				cairo_fill(cr);
1318 			}
1319 		}
1320 
1321 		if (GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->draw)
1322 			event_handled = GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->draw(widget, cr);
1323 #endif
1324 		if (master->visible_spacing) {
1325 			DBG_SIGNALS("bluefish_text_view_draw_event, paint visible spacing\n");
1326 			paint_spaces(btv, cr, &startvisible, &endvisible);
1327 		}
1328 
1329 		if (master->show_right_margin) {
1330 			GtkStyleContext *cntxt;
1331 			GdkRGBA rgba;
1332 			guint pix =
1333 				master->margin_pixels_per_char * main_v->props.right_margin_pos +
1334 				master->margin_pixels_block + master->margin_pixels_symbol + master->margin_pixels_chars;
1335 
1336 			cairo_set_line_width(cr, 1.0);	/* 1.0 looks the best, smaller gives a half-transparent color */
1337 			cntxt = gtk_widget_get_style_context(GTK_WIDGET(btv));
1338 			gtk_style_context_get_color(cntxt, gtk_widget_get_state_flags(GTK_WIDGET(btv)), &rgba);
1339 			gdk_cairo_set_source_rgba(cr, &rgba);
1340 			cairo_move_to(cr, pix, rect2y);
1341 			cairo_line_to(cr, pix, rect2y + rect.height);
1342 			cairo_stroke(cr);
1343 		}
1344 	}
1345 
1346 	return event_handled;
1347 }
1348 #else
bluefish_text_view_expose_event(GtkWidget * widget,GdkEventExpose * event)1349 static gboolean bluefish_text_view_expose_event(GtkWidget * widget, GdkEventExpose * event)
1350 {
1351 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
1352 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
1353 	gboolean event_handled = FALSE;
1354 	GdkWindow *wleft, *wtext;
1355 	GdkRectangle rect;
1356 	gint rect2y, rect2x;
1357 	GtkTextIter startvisible, endvisible;
1358 
1359 	cairo_t *cr = gdk_cairo_create(event->window);
1360 
1361 	wleft = gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_LEFT);
1362 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &rect);
1363 	gtk_text_view_get_line_at_y(GTK_TEXT_VIEW(widget), &startvisible, rect.y, NULL);
1364 	gtk_text_view_get_line_at_y(GTK_TEXT_VIEW(widget), &endvisible, rect.y + rect.height, NULL);
1365 	gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT, rect.x,
1366 										  rect.y, &rect2x, &rect2y);
1367 
1368 	if (event->window == wleft) {
1369 	   /******** the painting in the MARGIN area of the widget *********/
1370 		DBG_SIGNALS("bluefish_text_view_expose_event, GTK_TEXT_WINDOW_LEFT\n");
1371 		paint_margin(btv, cr, &startvisible, &endvisible);
1372 		event_handled = TRUE;
1373 	}
1374 	wtext = gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT);
1375 	if (event->window == wtext) {
1376 	   /******** the painting in the TEXT area of the widget ********/
1377 		if (gtk_widget_is_sensitive(GTK_WIDGET(btv))
1378 			&& (BFWIN(DOCUMENT(master->doc)->bfwin)->session->view_cline || main_v->props.highlight_cursor)) {
1379 			gint y2, x2;
1380 			GtkTextIter it;
1381 			GdkRectangle itrect;
1382 			DBG_SIGNALS("bluefish_text_view_expose_event, current line highlighting\n");
1383 			gtk_text_buffer_get_iter_at_mark(master->buffer, &it, gtk_text_buffer_get_insert(master->buffer));
1384 			gtk_text_view_get_iter_location((GtkTextView *) widget, &it, &itrect);
1385 			gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT,
1386 												  itrect.x, itrect.y, &x2, &y2);
1387 			if (BFWIN(DOCUMENT(master->doc)->bfwin)->session->view_cline) {
1388 				/*g_print("cline highlight, got itrect.y=%d, y2=%d, itrect.x=%d, x2=%d, itrect.height=%d, itrect.width=%d\n",itrect.y,y2,itrect.x, x2, itrect.height, itrect.width); */
1389 				gdk_cairo_set_source_color(cr, &st_cline_color);
1390 				/*g_print("draw cline rectangle %f:%f %f-%f\n",rect2x + .5, y2 + .5, (gfloat)(rect.width - 1), (gfloat)(itrect.height - 1)); */
1391 				cairo_rectangle(cr, (gfloat) rect2x, (gfloat) y2, (gfloat) (rect.width),
1392 								(gfloat) (itrect.height));
1393 				cairo_fill(cr);
1394 			}
1395 			if (main_v->props.highlight_cursor) {
1396 				gint width = itrect.width > 5 ? itrect.width : master->margin_pixels_per_char;
1397 				gdk_cairo_set_source_color(cr, &st_cursor_highlight_color);
1398 				cairo_rectangle(cr, (gfloat) x2 - width, (gfloat) y2, (gfloat) (width * 2),
1399 								(gfloat) itrect.height);
1400 				cairo_fill(cr);
1401 			}
1402 		}
1403 
1404 		if (GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->expose_event)
1405 			event_handled = GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->expose_event(widget, event);
1406 
1407 		if (master->visible_spacing) {
1408 			DBG_SIGNALS("bluefish_text_view_expose_event, paint visible spacing\n");
1409 			paint_spaces(btv, cr, &startvisible, &endvisible);
1410 		}
1411 
1412 		if (master->show_right_margin) {
1413 			guint pix = master->margin_pixels_per_char * main_v->props.right_margin_pos;
1414 			cairo_set_line_width(cr, 1.0);	/* 1.0 looks the best, smaller gives a half-transparent color */
1415 			gdk_cairo_set_source_color(cr,
1416 									   &gtk_widget_get_style(GTK_WIDGET(btv))->fg[gtk_widget_get_state
1417 																				  (GTK_WIDGET(btv))]);
1418 
1419 			cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height);
1420 			cairo_clip(cr);
1421 			cairo_move_to(cr, pix, rect2y);
1422 			cairo_line_to(cr, pix, rect2y + rect.height);
1423 			cairo_stroke(cr);
1424 		}
1425 	}
1426 
1427 	cairo_destroy(cr);
1428 
1429 	return event_handled;
1430 }
1431 #endif							/* gtk3 */
1432 
1433 static void
bftextview2_delete_range_lcb(GtkTextBuffer * buffer,GtkTextIter * obegin,GtkTextIter * oend,gpointer user_data)1434 bftextview2_delete_range_lcb(GtkTextBuffer * buffer, GtkTextIter * obegin,
1435 							 GtkTextIter * oend, gpointer user_data)
1436 {
1437 	BluefishTextView *btv = user_data;
1438 	gint so, eo, mso, meo, loop, offset;
1439 	GtkTextIter begin = *obegin, end = *oend;
1440 	DBG_SIGNALS("bftextview2_delete_range_lcb\n");
1441 
1442 	if (btv->master != btv) {
1443 		return;
1444 	}
1445 
1446 	so = gtk_text_iter_get_offset(obegin);	/* re-use the loop variable */
1447 	eo = gtk_text_iter_get_offset(oend);
1448 	DBG_SIGNALS("bftextview2_delete_range_lcb, delete from %d to %d\n", so, eo);
1449 	foundcache_update_offsets(BLUEFISH_TEXT_VIEW(btv->master), so, so - eo);
1450 
1451 
1452 	/* mark the surroundings of the text that will be deleted */
1453 
1454 	/* the 'word start' algorithm of pango becomes very slow in a situation where
1455 	   the file is filled with funny unicode characters such as 'box' symbol characters.
1456 	   This happens in search and replace with many replaces (this function is called for
1457 	   each replace).
1458 	   I have to see why this is. We could also mark from the beginning of the line, but that
1459 	   would be excessive on very long lines...... what is best?? */
1460 	loop = 0;
1461 	while (loop < 32 && gtk_text_iter_backward_char(&begin)
1462 		   && !g_unichar_isspace(gtk_text_iter_get_char(&begin)))
1463 		loop++;
1464 	loop = 0;
1465 	while (loop < 32 && gtk_text_iter_forward_char(&end) && !g_unichar_isspace(gtk_text_iter_get_char(&end)))
1466 		loop++;
1467 	mso = gtk_text_iter_get_offset(&begin);
1468 	meo = gtk_text_iter_get_offset(&end);
1469 	/*gtk_text_iter_backward_word_start(&begin);
1470 	   gtk_text_iter_forward_word_end(&end); */
1471 #ifdef MARKREGION
1472 	offset = so - eo;
1473 	markregion_delete(&btv->scanning, mso, meo + offset, offset);
1474 	DBG_MARKREGION("bftextview2_delete_range_lcb, apply needscanning (before offset is applied!) to %u:%u\n",
1475 				   gtk_text_iter_get_offset(&begin), gtk_text_iter_get_offset(&end));
1476 #ifdef HAVE_LIBENCHANT
1477 	markregion_delete(&btv->spellcheck, mso, meo + offset, offset);
1478 #endif
1479 #endif
1480 #ifdef NEEDSCANNING
1481 	gtk_text_buffer_apply_tag(buffer, btv->needscanning, &begin, &end);
1482 	DBG_SIGNALS("mark text from %d to %d as needscanning\n", gtk_text_iter_get_offset(&begin),
1483 				gtk_text_iter_get_offset(&end));
1484 #ifdef HAVE_LIBENCHANT
1485 	gtk_text_buffer_apply_tag(buffer, btv->needspellcheck, &begin, &end);
1486 	DBG_SPELL("mark text from %d to %d as needspellcheck\n", gtk_text_iter_get_offset(&begin),
1487 			  gtk_text_iter_get_offset(&end));
1488 #endif							/*HAVE_LIBENCHANT */
1489 #endif
1490 	btv->needremovetags = 0;
1491 }
1492 
1493 static void
bftextview2_delete_range_after_lcb(GtkTextBuffer * buffer,GtkTextIter * obegin,GtkTextIter * oend,gpointer user_data)1494 bftextview2_delete_range_after_lcb(GtkTextBuffer * buffer, GtkTextIter * obegin,
1495 								   GtkTextIter * oend, gpointer user_data)
1496 {
1497 	BluefishTextView *btv = user_data;
1498 	/* in delete_range_after the text has been altered, so obegin and oend now both point to
1499 	   the same location, where the text was deleted */
1500 
1501 	DBG_SIGNALS("bftextview2_delete_range_after_lcb, btv=%p, master=%p, needs_autocomp=%d\n", btv,
1502 				btv->master, btv->needs_autocomp);
1503 	if (BLUEFISH_TEXT_VIEW(btv->master)->enable_scanner && btv->needs_autocomp
1504 		&& BLUEFISH_TEXT_VIEW(btv->master)->auto_complete && (btv->autocomp
1505 															  || main_v->props.autocomp_popup_mode != 0)) {
1506 		DBG_AUTOCOMP("bftextview2_delete_range_after_lcb, before autocomp_run()\n");
1507 		autocomp_run(btv, FALSE);
1508 	}
1509 	DBG_AUTOCOMP("bftextview2_delete_range_after_lcb, after run, set needs_autocomp to FALSE\n");
1510 	btv->needs_autocomp = FALSE;
1511 
1512 	bftextview2_reset_user_idle_timer(btv);
1513 	if (btv->master != btv) {
1514 		return;
1515 	}
1516 	bftextview2_schedule_scanning(btv);
1517 
1518 
1519 	/* because compare_markregion_needscanning() compares needscanning and markregion code, the offset needs to be adjusted in both.
1520 	   for needscanning the offset is only adjusted in the 'after' callback */
1521 #ifdef MARKREGION
1522 #ifdef NEEDSCANNING
1523 	compare_markregion_needscanning(btv);
1524 #endif
1525 #endif
1526 }
1527 
last_undo_is_spacingtoclick(BluefishTextView * btv)1528 gboolean last_undo_is_spacingtoclick(BluefishTextView * btv)
1529 {
1530 	return (btv->spacingtoclickstart != -1 && btv->spacingtoclickend != -1
1531 			&& doc_unre_test_last_entry(btv->doc, UndoInsert, btv->spacingtoclickstart,
1532 										btv->spacingtoclickend));
1533 }
1534 
bluefish_text_view_remove_spacingtoclick(BluefishTextView * btv)1535 void bluefish_text_view_remove_spacingtoclick(BluefishTextView * btv)
1536 {
1537 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
1538 	if (master->spacingtoclickstart != -1 && master->spacingtoclickend != -1) {
1539 		if (doc_unre_test_last_entry
1540 			(master->doc, UndoInsert, master->spacingtoclickstart, master->spacingtoclickend)) {
1541 			/*g_print("bluefish_text_view_remove_spacingtoclick -> undo last spacing from %d to %d\n",master->spacingtoclickstart, master->spacingtoclickend); */
1542 			/* last text action in the document was a spacingtoclick, so undo it */
1543 			undo_doc(master->doc, TRUE);
1544 		}
1545 		master->spacingtoclickstart = -1;
1546 		master->spacingtoclickend = -1;
1547 	}
1548 }
1549 
spacingtoclick_insert_spacing(BluefishTextView * btv,gint numchars,GtkTextIter * iter)1550 static void spacingtoclick_insert_spacing(BluefishTextView * btv, gint numchars, GtkTextIter * iter)
1551 {
1552 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
1553 	if (numchars > 0) {
1554 		gchar *tmpstr;
1555 		/*g_print("inserting numchars=%d spaces\n",numchars); */
1556 		tmpstr = bf_str_repeat(" ", numchars);
1557 		master->spacingtoclickstart = gtk_text_iter_get_offset(iter);
1558 		master->spacingtoclickend = master->spacingtoclickstart + numchars;
1559 		gtk_text_buffer_insert(master->buffer, iter, tmpstr, -1);
1560 		g_free(tmpstr);
1561 	}
1562 }
1563 
1564 
spacingtoclick_handle_keypress(BluefishTextView * btv,GdkEventKey * kevent)1565 static gboolean spacingtoclick_handle_keypress(BluefishTextView * btv, GdkEventKey * kevent)
1566 {
1567 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
1568 	GtkTextMark *imark;
1569 	GtkTextIter iter;
1570 	imark = gtk_text_buffer_get_insert(master->buffer);
1571 	gtk_text_buffer_get_iter_at_mark(master->buffer, &iter, imark);
1572 
1573 	if (kevent->keyval == GDK_Right) {
1574 		g_print("spacingtoclick_handle_keypress, GDK_Right\n");
1575 		if (gtk_text_iter_ends_line(&iter)) {
1576 			gint curoffset = gtk_text_iter_get_offset(&iter);
1577 			gint requested = curoffset + 1;
1578 			gint oldstart = master->spacingtoclickstart;
1579 			gint oldend = master->spacingtoclickend;
1580 			g_print("oldstart=%d, oldend=%d, curoffset=%d\n", oldstart, oldend, curoffset);
1581 			bluefish_text_view_remove_spacingtoclick(master);
1582 			if (oldend == curoffset && oldstart > 0) {
1583 				gtk_text_buffer_get_iter_at_offset(master->buffer, &iter, oldstart);
1584 				/* increase current spacing with 1 character */
1585 				spacingtoclick_insert_spacing(master, requested - oldstart, &iter);
1586 				g_print("increased existing spacing at %d to %d\n", oldstart, requested - oldstart);
1587 			} else {
1588 				gtk_text_buffer_get_iter_at_offset(master->buffer, &iter, curoffset);
1589 				/* new spacing */
1590 				spacingtoclick_insert_spacing(master, requested - curoffset, &iter);
1591 				g_print("added new spacing at %d to %d\n", curoffset, requested - curoffset);
1592 			}
1593 			return TRUE;
1594 		}
1595 	} else if (kevent->keyval == GDK_Left) {
1596 		/* reduce spacing one character */
1597 		g_print("spacingtoclick_handle_keypress, GDK_Left\n");
1598 		if (gtk_text_iter_ends_line(&iter)) {
1599 			gint curoffset = gtk_text_iter_get_offset(&iter);
1600 			gint requested = curoffset - 1;
1601 			gint oldstart = master->spacingtoclickstart;
1602 			gint oldend = master->spacingtoclickend;
1603 			g_print("oldstart=%d, oldend=%d, curoffset=%d\n", oldstart, oldend, curoffset);
1604 			bluefish_text_view_remove_spacingtoclick(master);
1605 			if (oldend == curoffset && oldstart > 0 && oldstart < requested) {
1606 				gtk_text_buffer_get_iter_at_offset(master->buffer, &iter, oldstart);
1607 				/* decrease current spacing with 1 character */
1608 				spacingtoclick_insert_spacing(master, requested - oldstart, &iter);
1609 				g_print("re-inserted spacing at %d to %d\n", oldstart, requested - oldstart);
1610 				return TRUE;
1611 			}
1612 			if (requested == oldstart) {
1613 				/*removing will put the cursor in the requested spot */
1614 				return TRUE;
1615 			}
1616 			g_print
1617 				("GDK_Left, only removed existing spacing, no new spacing, oldend=%d, oldstart=%d, curoffset=%d, requested=%d\n",
1618 				 oldend, oldstart, curoffset, requested);
1619 		}
1620 	} else if (kevent->keyval == GDK_Up || kevent->keyval == GDK_Down) {
1621 		GdkRectangle loc, loc2;
1622 		gboolean ret;
1623 		g_print
1624 			("spacingtoclick_handle_keypress, GDK_Up or GDK_Down, iter is at cursor, gtk_text_iter_get_line()=%d, offset=%d\n",
1625 			 gtk_text_iter_get_line(&iter), gtk_text_iter_get_offset(&iter));
1626 		/* see if line above has same amount of characters */
1627 		gtk_text_view_get_iter_location(GTK_TEXT_VIEW(btv), &iter, &loc);
1628 		g_print("iter (at cursor) location, loc.x=%d, loc.y=%d\n", loc.x, loc.y);
1629 		if (kevent->keyval == GDK_Up) {
1630 			ret = gtk_text_iter_backward_line(&iter);
1631 		} else {
1632 			ret = gtk_text_iter_forward_line(&iter);
1633 		}
1634 		if (ret) {
1635 			gint offset;
1636 			g_print("after line forward/backward, line is %d\n", gtk_text_iter_get_line(&iter));
1637 			if (!gtk_text_iter_ends_line(&iter)) {
1638 				g_print("iter at line %d and offset %d does not end line, forward to line end\n",
1639 						gtk_text_iter_get_line(&iter), gtk_text_iter_get_offset(&iter));
1640 				gtk_text_iter_forward_to_line_end(&iter);
1641 			}
1642 			offset = gtk_text_iter_get_offset(&iter);
1643 			gtk_text_view_get_iter_location(GTK_TEXT_VIEW(btv), &iter, &loc2);
1644 			g_print("loc.x=%d, loc2.x=%d (line=%d, offset=%d)\n", loc.x, loc2.x,
1645 					gtk_text_iter_get_line(&iter), gtk_text_iter_get_offset(&iter));
1646 			if (master->spacingtoclickstart != -1 && master->spacingtoclickstart < offset
1647 				&& master->spacingtoclickend <= offset) {
1648 				/* removing the spacing will change the offset */
1649 				g_print
1650 					("there is already spacing before our current offset (start=%d, end=%d) which will be removed, so reduce offset by %d\n",
1651 					 master->spacingtoclickstart, master->spacingtoclickend,
1652 					 master->spacingtoclickend - master->spacingtoclickstart);
1653 				offset -= (master->spacingtoclickend - master->spacingtoclickstart);
1654 			}
1655 			bluefish_text_view_remove_spacingtoclick(master);
1656 			if (loc.x > loc2.x) {
1657 				gint numchars;
1658 				numchars = (loc.x - loc2.x) / master->margin_pixels_per_char;
1659 				gtk_text_buffer_get_iter_at_offset(master->buffer, &iter, offset);
1660 				g_print("insert %d spacing at offset %d (line %d)\n", numchars, offset,
1661 						gtk_text_iter_get_line(&iter));
1662 				spacingtoclick_insert_spacing(master, numchars, &iter);
1663 			}
1664 			g_print("place cursor at x=%d,y=%d\n", loc.x, loc2.y);
1665 			gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(btv), &iter, loc.x, loc2.y);
1666 			gtk_text_buffer_place_cursor(master->buffer, &iter);
1667 			gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(btv), imark);
1668 			return TRUE;
1669 		} else {
1670 			g_print("failed to forward/backward line\n");
1671 		}
1672 		bluefish_text_view_remove_spacingtoclick(master);
1673 	} else {
1674 		bluefish_text_view_remove_spacingtoclick(master);
1675 	}
1676 	return FALSE;
1677 }
1678 
bluefish_text_view_key_press_event(GtkWidget * widget,GdkEventKey * kevent)1679 static gboolean bluefish_text_view_key_press_event(GtkWidget * widget, GdkEventKey * kevent)
1680 {
1681 	gboolean retval;
1682 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
1683 	BluefishTextView *master = btv->master;
1684 	DBG_SIGNALS("bluefish_text_view_key_press_event, keyval=%d\n", kevent->keyval);
1685 
1686 	if (master->margin_pixels_per_char <= 0) {
1687 		calc_pixels_per_char(master);
1688 	}
1689 	/* following code handles key press events on the autocompletion popup */
1690 	if (btv->autocomp) {
1691 		if (acwin_check_keypress(btv, kevent)) {
1692 			btv->key_press_inserted_char = FALSE;
1693 			return TRUE;
1694 		}
1695 	}
1696 	/* following code handles the manual autocompletion popup accelerator key, default <ctrl><space> */
1697 	if (master->enable_scanner && (kevent->state & main_v->autocomp_accel_mods)
1698 		&& kevent->keyval == main_v->autocomp_accel_key) {
1699 		DBG_AUTOCOMP("bluefish_text_view_key_press_event, autocomp key combination\n");
1700 		autocomp_run(btv, TRUE);
1701 		return TRUE;
1702 	}
1703 	/* avoid the autocompletion popup for certain keys such as the delete key */
1704 	if (kevent->keyval != GDK_Delete
1705 		&& kevent->keyval != GDK_Up
1706 		&& kevent->keyval != GDK_Down
1707 		&& kevent->keyval != GDK_Left
1708 		&& kevent->keyval != GDK_Right
1709 		&& kevent->keyval != GDK_Page_Up
1710 		&& kevent->keyval != GDK_Page_Down
1711 		&& kevent->keyval != GDK_Home
1712 		&& kevent->keyval != GDK_End
1713 		&& kevent->keyval != GDK_Alt_L
1714 		&& kevent->keyval != GDK_Alt_R
1715 		&& kevent->keyval != GDK_Control_L
1716 		&& kevent->keyval != GDK_Control_R
1717 		&& !(kevent->state & GDK_CONTROL_MASK) && !(kevent->state & GDK_MOD1_MASK)) {
1718 		DBG_AUTOCOMP("bluefish_text_view_key_press_event, keyval=%d, state=%d, set needs_autocomp to TRUE\n",
1719 					 kevent->keyval, kevent->state);
1720 		btv->needs_autocomp = TRUE;
1721 	} else {
1722 		if (main_v->props.editor_spacingtoclick && !(kevent->state & GDK_CONTROL_MASK)
1723 			&& !(kevent->state & GDK_MOD1_MASK) && !(kevent->state & GDK_SHIFT_MASK)) {
1724 			g_print
1725 				("bluefish_text_view_key_press_event, handling spacingtoclick keypress, kevent->state=%d\n",
1726 				 kevent->state);
1727 			if (spacingtoclick_handle_keypress(btv, kevent)) {
1728 				return TRUE;
1729 			}
1730 		}
1731 
1732 	}
1733 	/* following code does smart cursor placement */
1734 	if (main_v->props.editor_smart_cursor && !(kevent->state & GDK_CONTROL_MASK)
1735 		&& ((kevent->keyval == GDK_Home) || (kevent->keyval == GDK_KP_Home)
1736 			|| (kevent->keyval == GDK_End)
1737 			|| (kevent->keyval == GDK_KP_End))) {
1738 		GtkTextMark *imark;
1739 		gboolean ret;
1740 		GtkTextIter iter, currentpos, linestart;
1741 
1742 		imark = gtk_text_buffer_get_insert(master->buffer);
1743 		gtk_text_buffer_get_iter_at_mark(master->buffer, &currentpos, imark);
1744 		iter = currentpos;
1745 		/* if you hold ALT and you   have wrapped text, bluefish will jump to the previous/next newline, else
1746 		   it will jump the the start/end of the wrapped part of the line */
1747 		if ((kevent->keyval == GDK_Home) || (kevent->keyval == GDK_KP_Home)) {
1748 			ret = bf_text_iter_line_start_of_text(btv, &iter, &linestart, !(kevent->state & GDK_MOD1_MASK));
1749 		} else {				/* (kevent->keyval == GDK_End) || (kevent->keyval == GDK_KP_End) */
1750 			ret = bf_text_iter_line_end_of_text(btv, &iter, &linestart, !(kevent->state & GDK_MOD1_MASK));
1751 		}
1752 		if (ret) {
1753 			if (gtk_text_iter_compare(&currentpos, &iter) == 0)
1754 				iter = linestart;
1755 			if (kevent->state & GDK_SHIFT_MASK)
1756 				gtk_text_buffer_move_mark(master->buffer, imark, &iter);
1757 			else
1758 				gtk_text_buffer_place_cursor(master->buffer, &iter);
1759 			gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(btv),
1760 											   gtk_text_buffer_get_insert(master->buffer));
1761 			return TRUE;
1762 		}
1763 	}
1764 	/* following code indents on tab */
1765 	if (main_v->props.editor_tab_indent_sel
1766 		&& (kevent->keyval == GDK_Tab || kevent->keyval == GDK_KP_Tab || kevent->keyval == GDK_ISO_Left_Tab)
1767 		&& (!(kevent->state & GDK_CONTROL_MASK))) {	/* shift-tab is also known as GDK_ISO_Left_Tab */
1768 		GtkTextIter so, eo;
1769 		gboolean have_selection;
1770 		have_selection = gtk_text_buffer_get_selection_bounds(master->buffer, &so, &eo);
1771 		if (have_selection) {
1772 			GtkTextIter eol1, eol2, sol1, sol2;
1773 			if (kevent->state & GDK_SHIFT_MASK) {
1774 				/* unindent block */
1775 				doc_indent_selection(master->doc, TRUE);
1776 				return TRUE;
1777 			}
1778 			if (gtk_text_iter_get_line(&so) != gtk_text_iter_get_line(&eo)) {
1779 				doc_indent_selection(master->doc, FALSE);
1780 				return TRUE;
1781 			}
1782 			/* if the start and end are *around* the text (so either at the start or end or
1783 			   in the indenting) we indent */
1784 			eol1 = eo;
1785 			sol2 = so;
1786 			if (bf_text_iter_line_end_of_text(btv, &eol1, &eol2, FALSE)
1787 				&& bf_text_iter_line_start_of_text(btv, &sol2, &sol1, FALSE)) {
1788 				if ((gtk_text_iter_equal(&so, &sol1) || gtk_text_iter_equal(&so, &sol2)
1789 					 || gtk_text_iter_in_range(&so, &sol1, &sol2))
1790 					&& (gtk_text_iter_equal(&eo, &eol1) || gtk_text_iter_equal(&eo, &eol2)
1791 						|| gtk_text_iter_in_range(&eo, &eol1, &eol2))) {
1792 					doc_indent_selection(master->doc, FALSE);
1793 					return TRUE;
1794 				}
1795 			}
1796 		}
1797 	}
1798 	/* following code replaces tab with spaces */
1799 	if ((kevent->keyval == GDK_Tab && !(kevent->state & GDK_SHIFT_MASK)
1800 		 && !(kevent->state & GDK_CONTROL_MASK))
1801 		&& BFWIN(DOCUMENT(master->doc)->bfwin)->session->editor_indent_wspaces) {
1802 		GtkTextMark *imark;
1803 		GtkTextIter iter;
1804 		gchar *string;
1805 		gint numchars;
1806 		/* replace the tab with spaces if the user wants that.
1807 		   However, some users want the tab key to arrive at the next tab stop. so if the tab width is
1808 		   4 and there are two characters already, bluefish should insert only 2 characters */
1809 		string = bf_str_repeat(" ", BFWIN(DOCUMENT(master->doc)->bfwin)->session->editor_tab_width);
1810 		imark = gtk_text_buffer_get_insert(master->buffer);
1811 		gtk_text_buffer_get_iter_at_mark(master->buffer, &iter, imark);
1812 		numchars =
1813 			BFWIN(DOCUMENT(master->doc)->bfwin)->session->editor_tab_width -
1814 			(gtk_text_iter_get_line_offset(&iter) %
1815 			 BFWIN(DOCUMENT(master->doc)->bfwin)->session->editor_tab_width);
1816 		gtk_text_buffer_insert(master->buffer, &iter, string, numchars);
1817 		g_free(string);
1818 		return TRUE;
1819 	}
1820 	/* following code closes brackets */
1821 	if (main_v->props.editor_auto_close_brackets &&
1822 		(kevent->keyval == '[' || kevent->keyval == '{' || kevent->keyval == '(')
1823 		&& !(kevent->state & GDK_CONTROL_MASK)) {
1824 		gboolean noclose = FALSE;
1825 		GtkTextMark *imark = gtk_text_buffer_get_insert(master->buffer);
1826 		if (main_v->props.editor_auto_close_brackets == 2 /* smart */ ) {
1827 			GtkTextIter iter;
1828 			gunichar uc;
1829 			/* check the character that follows the cursor */
1830 			gtk_text_buffer_get_iter_at_mark(master->buffer, &iter, imark);
1831 			uc = gtk_text_iter_get_char(&iter);
1832 			/*g_print("smart bracket closing, uc='%c'\n",(gchar)uc); */
1833 			if (uc != ' ' && uc != '\0' && uc != '\n' && uc != '\t') {
1834 				noclose = TRUE;
1835 			}
1836 		}
1837 		if (!noclose) {
1838 			const gchar *insert;
1839 			GtkTextIter tmpit;
1840 			if (kevent->keyval == '{')
1841 				insert = "{}";
1842 			else if (kevent->keyval == '[')
1843 				insert = "[]";
1844 			else
1845 				insert = "()";
1846 			gtk_text_buffer_insert_at_cursor(master->buffer, insert, 2);
1847 			gtk_text_buffer_get_iter_at_mark(master->buffer, &tmpit, imark);
1848 			if (gtk_text_iter_backward_char(&tmpit)) {
1849 				gtk_text_buffer_place_cursor(master->buffer, &tmpit);
1850 			}
1851 			return TRUE;
1852 		}
1853 	}
1854 	/* following code moves a selected block */
1855 	if (kevent->state & GDK_CONTROL_MASK) {
1856 		if (kevent->keyval == GDK_Up) {
1857 			doc_move_selection(master->doc, TRUE, TRUE);
1858 			return TRUE;
1859 		}
1860 		if (kevent->keyval == GDK_Down) {
1861 			doc_move_selection(master->doc, FALSE, TRUE);
1862 			return TRUE;
1863 		}
1864 	}
1865 	/* following code marks the location if the 'menu' key is used as if it was a right click button event */
1866 	if (kevent->keyval == GDK_KEY_Menu) {
1867 		GtkTextMark *imark = gtk_text_buffer_get_insert(master->buffer);
1868 		GtkTextIter tmpit;
1869 		gtk_text_buffer_get_iter_at_mark(master->buffer, &tmpit, imark);
1870 		main_v->bevent_doc = master->doc;
1871 		main_v->bevent_charoffset = gtk_text_iter_get_offset(&tmpit);
1872 	}
1873 
1874 	retval = GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->key_press_event(widget, kevent);
1875 	if (retval) {
1876 		DBG_SIGNALS("parent handled the event, set key_press_inserted_char to TRUE\n");
1877 		btv->key_press_inserted_char = TRUE;
1878 	}
1879 	return retval;
1880 }
1881 
1882 typedef enum {
1883 	foldtags_fold,
1884 	foldtags_expand,
1885 	foldtags_expand_hidden
1886 } Tfoldtags;
1887 
block_fold_tags(BluefishTextView * btv,Tfoundblock * fblock,Tfoldtags mode)1888 static void block_fold_tags(BluefishTextView * btv, Tfoundblock * fblock, Tfoldtags mode)
1889 {
1890 	GtkTextBuffer *buffer = btv->buffer;
1891 	GtkTextIter it1, it2, it3, it4;
1892 
1893 	bftextview2_get_iters_at_foundblock(buffer, fblock, &it1, &it2, &it3, &it4);
1894 	if (main_v->props.block_folding_mode == 1 && !gtk_text_iter_ends_line(&it2)
1895 		&& !gtk_text_iter_starts_line(&it2)) {
1896 		gtk_text_iter_forward_to_line_end(&it2);
1897 	}
1898 	if (gtk_text_iter_ends_line(&it4)) {
1899 		gtk_text_iter_forward_line(&it4);
1900 	}
1901 
1902 	if (mode != foldtags_fold) {
1903 		DBG_FOLD("block_fold_apply_tags, REMOVE folded tags from %d:%d\n", fblock->start1_o, fblock->end2_o);
1904 		gtk_text_buffer_remove_tag_by_name(buffer, "foldheader", &it1, &it2);
1905 		if (main_v->props.block_folding_mode == 0) {
1906 			if (mode == foldtags_expand)
1907 				gtk_text_buffer_remove_tag_by_name(buffer, "_folded_", &it2, &it3);
1908 			gtk_text_buffer_remove_tag_by_name(buffer, "foldheader", &it3, &it4);
1909 		} else if (main_v->props.block_folding_mode == 1 && mode == foldtags_expand) {
1910 			gtk_text_buffer_remove_tag_by_name(buffer, "_folded_", &it2, &it4);
1911 		}
1912 	} else {
1913 		DBG_FOLD("block_fold_apply_tags, APPLY folded tags from %d:%d\n", fblock->start1_o, fblock->end2_o);
1914 		gtk_text_buffer_apply_tag_by_name(buffer, "foldheader", &it1, &it2);
1915 		if (main_v->props.block_folding_mode == 0) {
1916 			gtk_text_buffer_apply_tag_by_name(buffer, "_folded_", &it2, &it3);
1917 			gtk_text_buffer_apply_tag_by_name(buffer, "foldheader", &it3, &it4);
1918 		} else if (main_v->props.block_folding_mode == 1) {
1919 			gtk_text_buffer_apply_tag_by_name(buffer, "_folded_", &it2, &it4);
1920 		}
1921 		/*g_print("done applying tags to fold block\n"); */
1922 	}
1923 }
1924 
1925 static void
reapply_folded_tag_to_folded_blocks(BluefishTextView * btv,Tfoundblock * fblock,GSequenceIter ** siter)1926 reapply_folded_tag_to_folded_blocks(BluefishTextView * btv, Tfoundblock * fblock, GSequenceIter ** siter)
1927 {
1928 	Tfound *found;
1929 	GSequenceIter *tsiter = *siter;
1930 	found = get_foundcache_next(btv, &tsiter);
1931 	DBG_FOLD("reapply_folded_tag from %d:%d, starting with found at %d\n", fblock->start1_o, fblock->start2_o,
1932 			 found ? found->charoffset_o : 0);
1933 	while (found && found->charoffset_o < fblock->start2_o) {
1934 		if (IS_FOUNDMODE_BLOCKPUSH(found) && found->fblock->folded) {
1935 			block_fold_tags(btv, found->fblock, foldtags_fold);
1936 		}
1937 		found = get_foundcache_next(btv, &tsiter);
1938 	}
1939 }
1940 
parent_block_is_folded(Tfoundblock * fblock)1941 static gboolean parent_block_is_folded(Tfoundblock * fblock)
1942 {
1943 	Tfoundblock *tmpfblock = (Tfoundblock *) fblock->parentfblock;
1944 	while (tmpfblock) {
1945 		if (tmpfblock->folded) {
1946 			DBG_FOLD("parent_block_is_folded, return TRUE\n");
1947 			return TRUE;
1948 		}
1949 		tmpfblock = (Tfoundblock *) tmpfblock->parentfblock;
1950 	}
1951 	return FALSE;
1952 }
1953 
1954 static void
bftextview2_block_toggle_fold(BluefishTextView * btv,Tfoundblock * fblock,GSequenceIter ** siter)1955 bftextview2_block_toggle_fold(BluefishTextView * btv, Tfoundblock * fblock, GSequenceIter ** siter)
1956 {
1957 	Tfoldtags mode;
1958 	fblock->folded = (!fblock->folded);
1959 	if (fblock->folded) {
1960 		mode = foldtags_fold;
1961 	} else {
1962 		mode = parent_block_is_folded(fblock) ? foldtags_expand_hidden : foldtags_expand;
1963 	}
1964 	DBG_FOLD("bftextview2_block_toggle_fold, block %d:%d has now folded=%d\n", fblock->start1_o,
1965 			 fblock->end2_o, fblock->folded);
1966 	block_fold_tags(btv, fblock, mode);
1967 	if (mode != foldtags_fold) {
1968 		reapply_folded_tag_to_folded_blocks(btv, fblock, siter);
1969 	}
1970 }
1971 
bftextview2_toggle_fold(BluefishTextView * btv,GtkTextIter * iter)1972 static void bftextview2_toggle_fold(BluefishTextView * btv, GtkTextIter * iter)
1973 {
1974 	Tfound *found;
1975 	GSequenceIter *siter;
1976 	GtkTextIter tmpiter;
1977 	guint offset, nextline_o;
1978 
1979 	if (!btv->bflang)
1980 		return;
1981 	tmpiter = *iter;
1982 	gtk_text_iter_set_line_offset(&tmpiter, 0);
1983 	offset = gtk_text_iter_get_offset(&tmpiter);
1984 	if (!gtk_text_iter_ends_line(&tmpiter)) {
1985 		gtk_text_iter_forward_to_line_end(&tmpiter);
1986 	}
1987 	nextline_o = gtk_text_iter_get_offset(&tmpiter);
1988 	/* returns the found PRIOR to iter, or the found excactly at iter,
1989 	   but this fails if the iter is the start of the buffer */
1990 	found = get_foundcache_at_offset(btv, offset, &siter);
1991 	if (!found) {
1992 		/* is this 'if' block still required? I think get_foundcache_at_offset() now returns the first iter already */
1993 		DBG_FOLD("no found, retrieve first iter\n");
1994 		found = get_foundcache_first(btv, &siter);
1995 	}
1996 	while (found && found->charoffset_o < nextline_o) {
1997 		if (IS_FOUNDMODE_BLOCKPUSH(found) && found->fblock->foldable && found->fblock->start1_o >= offset)
1998 			break;
1999 		found = get_foundcache_next(btv, &siter);	/* should be the first found AFTER iter */
2000 	}
2001 	/*while (found && (found->charoffset_o < offset || !found->pushedblock || !found->pushedblock->foldable)) {
2002 	   found = get_foundcache_next(btv, &siter); / * should be the first found AFTER iter * /
2003 	   if (found && found->pushedblock && found->pushedblock->foldable)
2004 	   break;
2005 	   } */
2006 	if (found && IS_FOUNDMODE_BLOCKPUSH(found) && found->fblock->start1_o >= offset
2007 		&& found->fblock->start1_o <= nextline_o && found->fblock->foldable) {
2008 		DBG_FOLD("toggle fold on found=%p\n", found);
2009 		bftextview2_block_toggle_fold(btv, found->fblock, &siter);
2010 	}
2011 }
2012 
2013 /* if we keep the tooltips enabled we trigger a race condition or something like that in
2014 the text hiding and query tooltip code. So we stop the tooltips, and enable them again
2015 in a low priority callback */
enable_tooltip_idle_lcb(gpointer data)2016 static gboolean enable_tooltip_idle_lcb(gpointer data)
2017 {
2018 	g_object_set(G_OBJECT(data), "has-tooltip", TRUE, NULL);
2019 	return FALSE;
2020 }
2021 
2022 /*
2023  * the 'name' pointer should be the identical pointer as the pointer found in st->blocks
2024  * it is not compared on it's contents but on the pointer address
2025  */
bftextview2_collapse_expand_toggle(BluefishTextView * btv,const gchar * name,gboolean collapse)2026 static void bftextview2_collapse_expand_toggle(BluefishTextView * btv, const gchar * name, gboolean collapse)
2027 {
2028 	GSequenceIter *siter = NULL;
2029 	Tfound *found;
2030 	g_object_set(btv, "has-tooltip", FALSE, NULL);
2031 	found = get_foundcache_first(btv, &siter);
2032 	while (found) {
2033 		if (IS_FOUNDMODE_BLOCKPUSH(found) && found->fblock->foldable && found->fblock->folded != collapse) {
2034 			if (name) {
2035 				if (name ==
2036 					g_array_index(btv->bflang->st->blocks, Tpattern_block,
2037 								  g_array_index(btv->bflang->st->matches, Tpattern,
2038 												found->fblock->patternum).block).name)
2039 					bftextview2_block_toggle_fold(btv, found->fblock, &siter);
2040 			} else {
2041 				bftextview2_block_toggle_fold(btv, found->fblock, &siter);
2042 			}
2043 		}
2044 		found = get_foundcache_next(btv, &siter);
2045 	}
2046 	g_idle_add_full(G_PRIORITY_LOW, enable_tooltip_idle_lcb, btv, NULL);
2047 }
2048 
bftextview2_collapse_lcb(GtkMenuItem * mitem,BluefishTextView * btv)2049 static void bftextview2_collapse_lcb(GtkMenuItem * mitem, BluefishTextView * btv)
2050 {
2051 	bftextview2_collapse_expand_toggle(btv, g_object_get_data(G_OBJECT(mitem), "block_name"), TRUE);
2052 }
2053 
bftextview2_expand_lcb(GtkMenuItem * mitem,BluefishTextView * btv)2054 static void bftextview2_expand_lcb(GtkMenuItem * mitem, BluefishTextView * btv)
2055 {
2056 	bftextview2_collapse_expand_toggle(btv, g_object_get_data(G_OBJECT(mitem), "block_name"), FALSE);
2057 }
2058 
bftextview2_fold_menu(BluefishTextView * btv)2059 static GtkWidget *bftextview2_fold_menu(BluefishTextView * btv)
2060 {
2061 	gint i;
2062 	GtkWidget *mitem, *menu = gtk_menu_new();
2063 	mitem = gtk_menu_item_new_with_label(_("Collapse all"));
2064 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
2065 	g_signal_connect(G_OBJECT(mitem), "activate", G_CALLBACK(bftextview2_collapse_lcb), btv);
2066 	mitem = gtk_menu_item_new_with_label(_("Expand all"));
2067 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
2068 	g_signal_connect(G_OBJECT(mitem), "activate", G_CALLBACK(bftextview2_expand_lcb), btv);
2069 
2070 	/* loop over the found blocks */
2071 	for (i = 0; i < (btv->bflang->st->blocks->len); i++) {
2072 		if (g_array_index(btv->bflang->st->blocks, Tpattern_block, i).name
2073 			&& g_array_index(btv->bflang->st->blocks, Tpattern_block, i).foldable) {
2074 			gchar *tmp = g_strdup_printf(_("Collapse %s"),
2075 										 g_array_index(btv->bflang->st->blocks, Tpattern_block, i).name);
2076 			mitem = gtk_menu_item_new_with_label(tmp);
2077 			g_free(tmp);
2078 			g_object_set_data(G_OBJECT(mitem), "block_name",
2079 							  g_array_index(btv->bflang->st->blocks, Tpattern_block, i).name);
2080 			gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
2081 			g_signal_connect(G_OBJECT(mitem), "activate", G_CALLBACK(bftextview2_collapse_lcb), btv);
2082 
2083 			tmp =
2084 				g_strdup_printf(_("Expand %s"),
2085 								g_array_index(btv->bflang->st->blocks, Tpattern_block, i).name);
2086 			mitem = gtk_menu_item_new_with_label(tmp);
2087 			g_free(tmp);
2088 			g_object_set_data(G_OBJECT(mitem), "block_name",
2089 							  g_array_index(btv->bflang->st->blocks, Tpattern_block, i).name);
2090 			gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
2091 			g_signal_connect(G_OBJECT(mitem), "activate", G_CALLBACK(bftextview2_expand_lcb), btv);
2092 		}
2093 	}
2094 
2095 
2096 	gtk_widget_show_all(menu);
2097 	/* only required for submenu's that have a radioitem ????? g_signal_connect(G_OBJECT(menu), "destroy", destroy_disposable_menu_cb, menu); */
2098 	return menu;
2099 }
2100 
2101 static void
bftextview2_get_iter_at_bevent(BluefishTextView * btv,GdkEventButton * bevent,GtkTextIter * iter)2102 bftextview2_get_iter_at_bevent(BluefishTextView * btv, GdkEventButton * bevent, GtkTextIter * iter)
2103 {
2104 	gint xpos, ypos;
2105 	GtkTextWindowType wintype;
2106 
2107 	wintype = gtk_text_view_get_window_type(GTK_TEXT_VIEW(btv), gtk_widget_get_window(GTK_WIDGET(btv)));
2108 	gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(btv), wintype, bevent->x, bevent->y, &xpos, &ypos);
2109 	xpos += gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_LEFT);
2110 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(btv), iter, xpos, ypos);
2111 }
2112 
select_from_line_to_eventy(BluefishTextView * btv,gint line,guint eventy)2113 static void select_from_line_to_eventy(BluefishTextView * btv, gint line, guint eventy)
2114 {
2115 	GtkTextIter so, eo;
2116 	gint x, y;
2117 	gtk_text_buffer_get_iter_at_line(btv->buffer, &so, line);
2118 	gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT, 0, eventy, &x, &y);
2119 	gtk_text_view_get_line_at_y(GTK_TEXT_VIEW(btv), &eo, y, &x);
2120 	gtk_text_iter_forward_to_line_end(&eo);
2121 	DEBUG_MSG("select_from_line_to_eventy, line=%d, eventy=%d,select from so=%d to eo=%d\n", line, eventy,
2122 			  gtk_text_iter_get_offset(&so), gtk_text_iter_get_offset(&eo));
2123 	gtk_text_buffer_select_range(btv->buffer, &so, &eo);
2124 }
2125 
bluefish_text_view_button_press_event(GtkWidget * widget,GdkEventButton * event)2126 static gboolean bluefish_text_view_button_press_event(GtkWidget * widget, GdkEventButton * event)
2127 {
2128 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
2129 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
2130 
2131 	if (main_v->props.editor_spacingtoclick) {
2132 		bluefish_text_view_remove_spacingtoclick(master);
2133 	}
2134 	if (master->margin_pixels_per_char <= 0) {
2135 		calc_pixels_per_char(master);
2136 	}
2137 
2138 	DBG_SIGNALS("bluefish_text_view_button_press_event, widget=%p, btv=%p, master=%p, x=%f, y=%f\n", widget,
2139 				btv, master, event->x, event->y);
2140 	btv->button_press_line = -1;
2141 	if (event->window == gtk_text_view_get_window(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_LEFT)) {
2142 
2143 		if (event->button == 1) {
2144 			gint x, y;
2145 			GtkTextIter it;
2146 
2147 			gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT, 0, event->y, &x,
2148 												  &y);
2149 			gtk_text_view_get_line_at_y(GTK_TEXT_VIEW(widget), &it, y, &x);
2150 
2151 			if (event->type == GDK_2BUTTON_PRESS && (event->x > (master->margin_pixels_chars))
2152 				&& (event->x < (master->margin_pixels_chars + master->margin_pixels_symbol))) {
2153 #if GTK_CHECK_VERSION(3,0,0)
2154 				cairo_region_t *region;
2155 				GdkWindow *window = gtk_text_view_get_window(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_LEFT);
2156 				bmark_toggle(btv->doc, gtk_text_iter_get_offset(&it), NULL, NULL);
2157 				region = gdk_window_get_clip_region(window);
2158 				gdk_window_invalidate_region(window, region, FALSE);
2159 				cairo_region_destroy(region);
2160 #else
2161 				GdkRegion *region;
2162 				bmark_toggle(btv->doc, gtk_text_iter_get_offset(&it), NULL, NULL);
2163 				/* redraw margin */
2164 				region = gdk_drawable_get_clip_region(event->window);
2165 				gdk_window_invalidate_region(event->window, region, FALSE);
2166 				gdk_region_destroy(region);
2167 #endif							/* gtk3 */
2168 
2169 				return TRUE;
2170 			}
2171 			if (btv->show_blocks && (event->x > (master->margin_pixels_chars + master->margin_pixels_symbol))) {	/* get the offset that equals the folding area */
2172 
2173 				gtk_text_view_get_line_at_y(GTK_TEXT_VIEW(widget), &it, y, &x);
2174 				DBG_FOLD("fold/unfold at offset %d (line %d)\n", gtk_text_iter_get_offset(&it),
2175 						 gtk_text_iter_get_line(&it));
2176 				bftextview2_toggle_fold(btv, &it);
2177 				return TRUE;
2178 			}
2179 			if (event->x < master->margin_pixels_chars) {
2180 				master->button_press_line = gtk_text_iter_get_line(&it);
2181 			}
2182 		} else if (event->button == 3 && master->show_blocks && (event->x > master->margin_pixels_chars)) {
2183 #if GTK_CHECK_VERSION(3,22,0)
2184 			gtk_menu_popup_at_pointer(GTK_MENU(bftextview2_fold_menu(btv)), NULL);
2185 #else
2186 			gtk_menu_popup(GTK_MENU(bftextview2_fold_menu(btv)), NULL, NULL, NULL, NULL, event->button,
2187 						   event->time);
2188 #endif
2189 			return TRUE;
2190 		}
2191 	}
2192 	if (event->button == 1) {
2193 
2194 		if (event->type == GDK_3BUTTON_PRESS && event->window == gtk_text_view_get_window(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT)) {
2195 			/* select current line */
2196 			GtkTextIter sit1, sit2;
2197 			gint x, y;
2198 			gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT, 0, event->y, &x,
2199 												  &y);
2200 			gtk_text_view_get_line_at_y(GTK_TEXT_VIEW(widget), &sit1, y, &x);
2201 			sit2 = sit1;
2202 			gtk_text_iter_forward_to_line_end(&sit2);
2203 			gtk_text_buffer_select_range(btv->buffer, &sit1, &sit2);
2204 		}
2205 
2206 		if (master->show_mbhl) {
2207 			btv->needs_blockmatch = TRUE;
2208 			if (!btv->mark_set_idle)
2209 				btv->mark_set_idle = g_idle_add_full(G_PRIORITY_HIGH_IDLE, mark_set_idle_lcb, btv, NULL);
2210 		}
2211 
2212 		if (main_v->props.editor_spacingtoclick) {
2213 			gint bufx, bufy;
2214 			gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT, event->x,
2215 												  event->y, &bufx, &bufy);
2216 			master->spacingtoclickend = bufx;
2217 		}
2218 
2219 	} else if (event->button == 3) {
2220 		GtkTextIter iter;
2221 		/* store the location of the right mouse button click for menu items like 'edit tag'
2222 		   or 'edit color' */
2223 		bftextview2_get_iter_at_bevent(btv, event, &iter);
2224 		main_v->bevent_doc = master->doc;
2225 		main_v->bevent_charoffset = gtk_text_iter_get_offset(&iter);
2226 	}
2227 	/* here we ask any plugins to do any processing */
2228 	if (main_v->doc_view_button_press_cbs) {
2229 		GSList *tmplist = main_v->doc_view_button_press_cbs;
2230 		while (tmplist) {
2231 			void *(*func) () = tmplist->data;
2232 			DEBUG_MSG
2233 				("bluefish_text_view_button_press_event, calling plugin func %p for widget %p, master %p and doc %p\n",
2234 				 tmplist->data, widget, master, master->doc);
2235 			func(widget, event, (Tdocument *) master->doc);
2236 			tmplist = g_slist_next(tmplist);
2237 		}
2238 	}
2239 	DEBUG_MSG("bluefish_text_view_button_press_event, call parent button_press_event for widget %p\n",
2240 			  widget);
2241 	return GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->button_press_event(widget, event);
2242 }
2243 
bluefish_text_view_motion_notify_event(GtkWidget * widget,GdkEventMotion * event)2244 static gboolean bluefish_text_view_motion_notify_event(GtkWidget * widget, GdkEventMotion * event)
2245 {
2246 	if (((BluefishTextView *) widget)->button_press_line != -1
2247 		&& event->x < ((BluefishTextView *) ((BluefishTextView *) widget)->master)->margin_pixels_chars) {
2248 		DBG_SIGNALS("bluefish_text_view_motion_notify_event, event->x=%d, event->y=%d\n", event->x, event->y);
2249 		select_from_line_to_eventy((BluefishTextView *) widget,
2250 								   ((BluefishTextView *) widget)->button_press_line, event->y);
2251 		return TRUE;
2252 	}
2253 	return GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->motion_notify_event(widget, event);
2254 }
2255 
bluefish_text_view_button_release_event(GtkWidget * widget,GdkEventButton * event)2256 static gboolean bluefish_text_view_button_release_event(GtkWidget * widget, GdkEventButton * event)
2257 {
2258 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(widget)->master;
2259 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
2260 	if (((BluefishTextView *) widget)->button_press_line != -1
2261 		&& event->x < ((BluefishTextView *) ((BluefishTextView *) widget)->master)->margin_pixels_chars
2262 		&& event->button == 1) {
2263 		if (!gtk_text_buffer_get_has_selection(((BluefishTextView *) widget)->buffer)) {
2264 			DBG_SIGNALS("bluefish_text_view_button_release_event, event->x=%d, event->y=%d\n", event->x,
2265 						event->y);
2266 			select_from_line_to_eventy((BluefishTextView *) widget,
2267 									   ((BluefishTextView *) widget)->button_press_line, event->y);
2268 			((BluefishTextView *) widget)->button_press_line = -1;
2269 			return TRUE;
2270 		}
2271 		((BluefishTextView *) widget)->button_press_line = -1;
2272 	}
2273 
2274 	if (event->button == 1 && main_v->props.editor_spacingtoclick) {
2275 		gint bufx, bufy, numchars;
2276 		GtkTextIter iter;
2277 
2278 		gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT, event->x, event->y,
2279 											  &bufx, &bufy);
2280 		if (master->spacingtoclickend == bufx) {
2281 			/*g_print("bufx=%d,bufy=%d\n",bufx,bufy); */
2282 			gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(btv), &iter, bufx, bufy);
2283 			if (gtk_text_iter_ends_line(&iter)) {
2284 				GdkRectangle loc;
2285 				/* the difference between the location of the iter and the requested locations defines the number of characters
2286 				   that need to be inserted */
2287 				gtk_text_view_get_iter_location(GTK_TEXT_VIEW(btv), &iter, &loc);
2288 				/*g_print("iter at line %d, line offset=%d\n",gtk_text_iter_get_line(&iter),gtk_text_iter_get_line_offset(&iter)); */
2289 				numchars = ((bufx - loc.x) / master->margin_pixels_per_char);
2290 				spacingtoclick_insert_spacing(master, numchars, &iter);
2291 			} else {
2292 				master->spacingtoclickend = -1;
2293 			}
2294 		} else {
2295 			master->spacingtoclickend = -1;
2296 		}
2297 	}
2298 
2299 	return GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->button_release_event(widget, event);
2300 }
2301 
get_line_indenting(GtkTextBuffer * buffer,GtkTextIter * iter,gboolean prevline)2302 gchar *get_line_indenting(GtkTextBuffer * buffer, GtkTextIter * iter, gboolean prevline)
2303 {
2304 	gchar *string;
2305 	gchar *indenting;
2306 	GtkTextIter itstart, itend;
2307 
2308 	itstart = itend = *iter;
2309 	if (prevline) {
2310 		gtk_text_iter_backward_line(&itend);
2311 	}
2312 	/* set to the beginning of the line */
2313 	gtk_text_iter_set_line_index(&itstart, 0);
2314 	string = gtk_text_buffer_get_text(buffer, &itstart, &itend, TRUE);
2315 	if (!string)
2316 		return NULL;
2317 	/*g_print("get_line_indenting, got line '%s' len %d\n",string,strlen(string)); */
2318 	/* now count the indenting in this string */
2319 	indenting = string;
2320 	while (*indenting == '\t' || *indenting == ' ') {
2321 		indenting++;
2322 	}
2323 	/* ending search, non-whitespace found, so terminate at this position */
2324 	*indenting = '\0';
2325 	return string;
2326 }
2327 
2328 /*
2329 adds the (smart) indenting after an enter key is released
2330 iter is set to the cursor position
2331 */
2332 
auto_add_indenting(BluefishTextView * btv,GtkTextIter * iter)2333 static inline void auto_add_indenting(BluefishTextView * btv, GtkTextIter * iter)
2334 {
2335 	gchar *string;
2336 	gchar lastchar = '\0';
2337 	gboolean next_is_outdent = FALSE, prev_is_outdent = FALSE, prev_is_indent = FALSE;
2338 	GtkTextIter iter2 = *iter;
2339 	string = get_line_indenting(btv->buffer, iter, TRUE);
2340 	if (!string)
2341 		return;
2342 
2343 	if (main_v->props.smartindent) {
2344 		gtk_text_iter_backward_chars(&iter2, 2);
2345 		gunichar uc = gtk_text_iter_get_char(&iter2);
2346 		lastchar = (uc < 255) ? uc : 127;	/* 127 = DEL is a not used character */
2347 		if (BLUEFISH_TEXT_VIEW(btv->master)->bflang && lastchar != '\0') {
2348 			if (BLUEFISH_TEXT_VIEW(btv->master)->bflang->smartoutdentchars) {
2349 				next_is_outdent =
2350 					(strchr
2351 					 (BLUEFISH_TEXT_VIEW(btv->master)->bflang->smartoutdentchars,
2352 					  (char) gtk_text_iter_get_char(iter)) != NULL);
2353 				prev_is_outdent =
2354 					(strchr(BLUEFISH_TEXT_VIEW(btv->master)->bflang->smartoutdentchars, lastchar) != NULL);
2355 			}
2356 			if (BLUEFISH_TEXT_VIEW(btv->master)->bflang->smartindentchars) {
2357 				prev_is_indent =
2358 					(strchr(BLUEFISH_TEXT_VIEW(btv->master)->bflang->smartindentchars, lastchar) != NULL);
2359 			}
2360 		}
2361 		/*g_print("auto_add_indenting, previous indenting '%s' strlen=%d\n",string,(int)strlen(string));
2362 		   g_print("auto_add_indenting, lastchar=%c, smartindentchars=%s\n",lastchar, btv->bflang->smartindentchars);
2363 		   g_print("next_is_outdent=%d, prev_is_indent=%d, prev_is_outdent=%d\n",next_is_outdent, prev_is_indent, prev_is_outdent); */
2364 		if (!next_is_outdent && prev_is_indent) {
2365 			gchar *tmp, *tmp2;
2366 			if (BFWIN(DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->bfwin)->session->editor_indent_wspaces)
2367 				tmp2 =
2368 					bf_str_repeat(" ",
2369 								  BFWIN(DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->bfwin)->
2370 								  session->editor_tab_width);
2371 			else
2372 				tmp2 = g_strdup("	");
2373 			tmp = g_strconcat(string, tmp2, NULL);
2374 			g_free(string);
2375 			g_free(tmp2);
2376 			string = tmp;
2377 		} else if (main_v->props.adv_smart_indent_mode == 2 && prev_is_outdent) {
2378 			/* if main_v->props.adv_smart_indent_mode is set to 2 bluefish will unindent if you
2379 			   hit enter after a closing bracket like }
2380 			 */
2381 			gint len;
2382 			/* reduce the indenting in 'string' by one level */
2383 			len = strlen(string);
2384 			if (string[len - 1] == '\t') {
2385 				string[len - 1] = '\0';
2386 			} else if (string[len - 1] == ' ') {
2387 				gint i = len - 1;
2388 				while (string[i] == ' '
2389 					   && i > len - 1 - BFWIN(DOCUMENT(btv->doc)->bfwin)->session->editor_tab_width
2390 					   && i >= 0) {
2391 					i--;
2392 				}
2393 				string[i] = '\0';
2394 			}
2395 		}
2396 	}
2397 	if (string && string[0] != '\0') {
2398 		gboolean in_paste = DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->in_paste_operation;
2399 		/*g_print("bluefish_text_view_key_release_event, autoindent, insert indenting\n"); */
2400 		/* a dirty trick: if in_paste_operation is set, there will be no call
2401 		   for doc_unre_new_group when indenting is inserted */
2402 		if (!in_paste)
2403 			DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->in_paste_operation = TRUE;
2404 		gtk_text_buffer_insert(btv->buffer, iter, string, -1);
2405 		if (!in_paste)
2406 			DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->in_paste_operation = FALSE;
2407 		btv->insert_was_auto_indent = TRUE;
2408 	}
2409 	g_free(string);
2410 }
2411 
auto_decrease_indenting(BluefishTextView * btv,GtkTextIter * iter)2412 static inline void auto_decrease_indenting(BluefishTextView * btv, GtkTextIter * iter)
2413 {
2414 	GtkTextIter itend, itstart;
2415 	gunichar uc;
2416 	/* reduce the indenting one level back */
2417 	itend = *iter;
2418 	gtk_text_iter_backward_char(&itend);
2419 	itstart = itend;
2420 	gtk_text_iter_backward_char(&itstart);
2421 	uc = gtk_text_iter_get_char(&itstart);
2422 	/*g_print("found indenting char '%c'\n",uc); */
2423 	if (uc == '\t') {
2424 		gtk_text_buffer_delete(btv->buffer, &itstart, &itend);
2425 	} else if (uc == ' ') {
2426 		int i = 1;
2427 		/* if a space was the previous char, we need N spaces to unindent */
2428 		while (uc == ' ' && i < BFWIN(DOCUMENT(btv->doc)->bfwin)->session->editor_tab_width) {
2429 			gtk_text_iter_backward_char(&itstart);
2430 			uc = gtk_text_iter_get_char(&itstart);
2431 			i++;
2432 		}
2433 
2434 		gtk_text_buffer_delete(btv->buffer, &itstart, &itend);
2435 	}
2436 }
2437 
auto_indent_blockstackbased(BluefishTextView * btv)2438 static inline void auto_indent_blockstackbased(BluefishTextView * btv)
2439 {
2440 	gchar *tmp2;
2441 	GtkTextIter iter;
2442 	guint offset, num = 0;
2443 	Tfound *found;
2444 	GSequenceIter *siter;
2445 	Tfoundblock *fblock;
2446 	gboolean in_paste;
2447 	DBG_MSG("auto_indent_blockstackbased, started\n");
2448 	gtk_text_buffer_get_iter_at_mark(btv->buffer, &iter, gtk_text_buffer_get_insert(btv->buffer));
2449 	offset = gtk_text_iter_get_offset(&iter);
2450 	found = get_foundcache_at_offset(BLUEFISH_TEXT_VIEW(btv->master), offset, &siter);
2451 	DBG_MSG("auto_indent_blockstackbased, found=%p\n", found);
2452 	if (!found || found->charoffset_o > offset)
2453 		return;
2454 	fblock = found->fblock;
2455 	if (found->numblockchange < 0)
2456 		num = found->numblockchange;
2457 	while (fblock) {
2458 		fblock = (Tfoundblock *) fblock->parentfblock;
2459 		num++;
2460 	}
2461 	DBG_MSG("auto_indent_blockstackbased, num blocks=%d\n", num);
2462 	if (num <= 0)
2463 		return;
2464 	if (BFWIN(DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->bfwin)->session->editor_indent_wspaces)
2465 		tmp2 =
2466 			bf_str_repeat(" ",
2467 						  BFWIN(DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->bfwin)->
2468 						  session->editor_tab_width * num);
2469 	else
2470 		tmp2 = bf_str_repeat("\t", num);
2471 	in_paste = DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->in_paste_operation;
2472 	if (!in_paste)
2473 		DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->in_paste_operation = TRUE;
2474 	gtk_text_buffer_insert(btv->buffer, &iter, tmp2, -1);
2475 	if (!in_paste)
2476 		DOCUMENT(BLUEFISH_TEXT_VIEW(btv->master)->doc)->in_paste_operation = FALSE;
2477 	btv->insert_was_auto_indent = TRUE;
2478 }
2479 
bluefish_text_view_key_release_event(GtkWidget * widget,GdkEventKey * kevent)2480 static gboolean bluefish_text_view_key_release_event(GtkWidget * widget, GdkEventKey * kevent)
2481 {
2482 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
2483 	gboolean prev_insert_was_auto_indent = btv->insert_was_auto_indent;
2484 	btv->insert_was_auto_indent = FALSE;
2485 
2486 	DBG_SIGNALS("bluefish_text_view_key_release_event for widget %p, keyval=%d, needs_blockmatch=%d\n",
2487 				widget, kevent->keyval, btv->needs_blockmatch);
2488 	DBG_BLOCKMATCH("bluefish_text_view_key_release_event, master->show_bhl=%d, mark_set_idle=%d, \n",
2489 				   BLUEFISH_TEXT_VIEW(btv->master)->show_mbhl, btv->mark_set_idle);
2490 	if (BLUEFISH_TEXT_VIEW(btv->master)->show_mbhl && !btv->mark_set_idle && btv->needs_blockmatch) {
2491 		btv->mark_set_idle = g_idle_add_full(G_PRIORITY_HIGH_IDLE, mark_set_idle_lcb, btv, NULL);
2492 	}
2493 
2494 	/* sometimes we receive a release event for a key that was not pressed in the textview widget!
2495 	   for example if you use the keyboard to navigate the menu, and press enter to activate an item, a
2496 	   key release event is received in the textview widget.... so we have to check that ! */
2497 	if (!btv->key_press_inserted_char)
2498 		return GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->key_release_event(widget, kevent);
2499 	btv->key_press_inserted_char = FALSE;	/* after the check we set this to FALSE */
2500 
2501 	if (!BLUEFISH_TEXT_VIEW(btv->master)->auto_indent)
2502 		return GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->key_release_event(widget, kevent);
2503 	/*g_print("bluefish_text_view_key_release_event, working on keyval %d\n",kevent->keyval); */
2504 
2505 	if (main_v->props.smartindent == 2 && (kevent->keyval == GDK_Return || kevent->keyval == GDK_KP_Enter)
2506 		&& !((kevent->state & GDK_SHIFT_MASK) || (kevent->state & GDK_CONTROL_MASK)
2507 			 || (kevent->state & GDK_MOD1_MASK))) {
2508 		/* 2 = indent based on the number of blocks on the stack */
2509 		g_print("indent blockstackbased\n");
2510 		auto_indent_blockstackbased(btv);
2511 	} else {
2512 		GtkTextIter iter;
2513 		gtk_text_buffer_get_iter_at_mark(btv->buffer, &iter, gtk_text_buffer_get_insert(btv->buffer));
2514 		if ((kevent->keyval == GDK_Return || kevent->keyval == GDK_KP_Enter)
2515 			&& !((kevent->state & GDK_SHIFT_MASK) || (kevent->state & GDK_CONTROL_MASK)
2516 				 || (kevent->state & GDK_MOD1_MASK))) {
2517 			auto_add_indenting(btv, &iter);
2518 		} else if (main_v->props.smartindent == 1 && prev_insert_was_auto_indent
2519 				   && btv->bflang && btv->bflang->smartoutdentchars
2520 				   && strchr(btv->bflang->smartoutdentchars, kevent->keyval) != NULL) {
2521 			auto_decrease_indenting(btv, &iter);
2522 		}
2523 	}
2524 	return GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->key_release_event(widget, kevent);
2525 }
2526 
2527 /* called for example by doc_reload() */
bluefish_text_view_scan_cleanup(BluefishTextView * btv)2528 void bluefish_text_view_scan_cleanup(BluefishTextView * btv)
2529 {
2530 	cleanup_scanner(btv);
2531 }
2532 
bluefish_text_view_rescan(BluefishTextView * btv)2533 void bluefish_text_view_rescan(BluefishTextView * btv)
2534 {
2535 	DBG_MSG("bluefish_text_view_rescan, btv=%p, lang=%p\n", btv, btv->bflang);
2536 	cleanup_scanner(btv->master);
2537 	if (BLUEFISH_TEXT_VIEW(btv->master)->bflang) {
2538 		GtkTextIter start, end;
2539 		GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv->master));
2540 		gtk_text_buffer_get_bounds(buffer, &start, &end);
2541 #ifdef MARKREGION
2542 		markregion_nochange(&btv->scanning, gtk_text_iter_get_offset(&start), gtk_text_iter_get_offset(&end));
2543 		DBG_MARKREGION("bluefish_text_view_rescan, apply needscanning to %d:%d\n",
2544 					   gtk_text_iter_get_offset(&start), gtk_text_iter_get_offset(&end));
2545 #ifdef HAVE_LIBENCHANT
2546 		markregion_nochange(&btv->spellcheck, gtk_text_iter_get_offset(&start),
2547 							gtk_text_iter_get_offset(&end));
2548 #endif
2549 #endif
2550 #ifdef NEEDSCANNING
2551 		gtk_text_buffer_apply_tag(buffer, BLUEFISH_TEXT_VIEW(btv->master)->needscanning, &start, &end);
2552 #ifdef HAVE_LIBENCHANT
2553 		DBG_SPELL("bluefish_text_view_rescan, mark all with needspellcheck\n");
2554 		gtk_text_buffer_apply_tag(buffer, BLUEFISH_TEXT_VIEW(btv->master)->needspellcheck, &start, &end);
2555 #endif							/*HAVE_LIBENCHANT */
2556 #endif
2557 		btv->needremovetags = 0;
2558 		bftextview2_schedule_scanning(btv);
2559 	}
2560 }
2561 
2562 	/* returns TRUE if
2563 	   there is a selection and a comment start and end is inside the selection
2564 	   OR no selection and cursor is inside a comment */
bluefish_text_view_in_comment(BluefishTextView * btv,GtkTextIter * its,GtkTextIter * ite)2565 gboolean bluefish_text_view_in_comment(BluefishTextView * btv, GtkTextIter * its, GtkTextIter * ite)
2566 {
2567 	GtkTextIter tmpits, tmpite;
2568 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv));
2569 	GtkTextTag *comment_tag = gtk_text_tag_table_lookup(langmgr_get_tagtable(), "comment");
2570 	if (gtk_text_buffer_get_selection_bounds(buffer, &tmpits, &tmpite)) {
2571 		*its = tmpits;
2572 		*ite = tmpite;
2573 		gtk_text_iter_order(&tmpits, &tmpite);
2574 		DEBUG_MSG("bluefish_text_view_in_comment, testing selection from %d:%d\n",
2575 				  gtk_text_iter_get_offset(&tmpits), gtk_text_iter_get_offset(&tmpite));
2576 		/* first test if the selection starts the tag and selection ends ends the tag */
2577 		if (gtk_text_iter_begins_tag(&tmpits, comment_tag)) {
2578 			/*g_print("tmpite at %d is_end=%d, ends_tag=%d, ends_line=%d\n", gtk_text_iter_get_offset(&tmpite), gtk_text_iter_is_end(&tmpite), gtk_text_iter_ends_tag(&tmpite, comment_tag), gtk_text_iter_ends_line(&tmpite)); */
2579 			if (gtk_text_iter_ends_tag(&tmpite, comment_tag) || gtk_text_iter_is_end(&tmpite)) {
2580 				DEBUG_MSG("bluefish_text_view_in_comment, selection %d:%d toggles comment, return TRUE\n",
2581 						  gtk_text_iter_get_offset(&tmpits), gtk_text_iter_get_offset(&tmpite));
2582 				return TRUE;
2583 			}
2584 			/* in line comments, a comment may start after the newline, but the selection may not include the newline */
2585 			if (gtk_text_iter_ends_line(&tmpite)) {
2586 				gtk_text_iter_forward_line(&tmpite);
2587 				/*g_print("tmpite at %d is_end=%d, ends_tag=%d, ends_line=%d\n", gtk_text_iter_get_offset(&tmpite), gtk_text_iter_is_end(&tmpite), gtk_text_iter_ends_tag(&tmpite, comment_tag), gtk_text_iter_ends_line(&tmpite)); */
2588 				if (gtk_text_iter_ends_tag(&tmpite, comment_tag) || gtk_text_iter_is_end(&tmpite)) {
2589 					DEBUG_MSG
2590 						("bluefish_text_view_in_comment, selection %d:%d (including newline) toggles comment, return TRUE\n",
2591 						 gtk_text_iter_get_offset(&tmpits), gtk_text_iter_get_offset(&tmpite));
2592 					return TRUE;
2593 				}
2594 			}
2595 		}
2596 		DEBUG_MSG("bluefish_text_view_in_comment, selection %d:%d does NOT toggle comment, return FALSE\n",
2597 				  gtk_text_iter_get_offset(&tmpits), gtk_text_iter_get_offset(&tmpite));
2598 		return FALSE;
2599 	} else {
2600 		gboolean retval;
2601 		gtk_text_buffer_get_iter_at_mark(buffer, &tmpits, gtk_text_buffer_get_insert(buffer));
2602 		/* for line comments the \n is usually not part of the comment anymore, so move back one char */
2603 		if (!gtk_text_iter_starts_line(&tmpits) && gtk_text_iter_ends_line(&tmpits))
2604 			gtk_text_iter_backward_char(&tmpits);
2605 		retval = gtk_text_iter_has_tag(&tmpits, comment_tag);
2606 		*ite = *its = tmpits;
2607 		if (retval) {
2608 			gtk_text_iter_forward_to_tag_toggle(ite, comment_tag);
2609 			gtk_text_iter_backward_to_tag_toggle(its, comment_tag);
2610 			return TRUE;
2611 		}
2612 	}
2613 	return FALSE;
2614 }
2615 
bluefish_text_view_get_comment(BluefishTextView * btv,GtkTextIter * it,Tcomment_type preferred_type)2616 Tcomment *bluefish_text_view_get_comment(BluefishTextView * btv, GtkTextIter * it,
2617 										 Tcomment_type preferred_type)
2618 {
2619 	/* get the context, and then retrieve the preferred comment type for that context */
2620 	GQueue *contextstack;
2621 	GList *tmplist;
2622 	guint16 contextnum = 0;
2623 
2624 	if (!btv->bflang)
2625 		return NULL;
2626 
2627 	if (!btv->bflang->st)
2628 		return NULL;
2629 
2630 	if (btv->master != btv)
2631 		g_warning("bluefish_text_view_get_comment should only be called for the master widget\n");
2632 
2633 	contextstack = get_contextstack_at_position(btv, it);
2634 /*	g_print("bluefish_text_view_get_comment, got contextstack %p with len %d\n",contextstack,contextstack->length);*/
2635 	if (!contextstack)
2636 		return NULL;
2637 
2638 	for (tmplist = g_list_first(contextstack->head); contextnum != 1; tmplist = g_list_next(tmplist)) {
2639 		guint8 line, block;
2640 		if (tmplist) {
2641 			contextnum = GPOINTER_TO_INT(tmplist->data);
2642 		} else {
2643 			contextnum = 1;
2644 		}
2645 		DEBUG_MSG("bluefish_text_view_get_comment, contextnum=%d\n", contextnum);
2646 		line = g_array_index(btv->bflang->st->contexts, Tcontext, contextnum).comment_line;
2647 		block = g_array_index(btv->bflang->st->contexts, Tcontext, contextnum).comment_block;
2648 		DEBUG_MSG
2649 			("bluefish_text_view_get_comment, type %d (line) has index %d, type %d (block) has index %d\n",
2650 			 comment_type_line, line, comment_type_block, block);
2651 		if (line == COMMENT_INDEX_NONE && block == COMMENT_INDEX_NONE)
2652 			return NULL;
2653 
2654 		if ((line == COMMENT_INDEX_INHERIT && block == COMMENT_INDEX_INHERIT)
2655 			|| (line == COMMENT_INDEX_INHERIT && block == COMMENT_INDEX_NONE)
2656 			|| (line == COMMENT_INDEX_NONE && block == COMMENT_INDEX_INHERIT)
2657 			)
2658 			continue;
2659 		DEBUG_MSG("preferred_type %d\n", preferred_type);
2660 		if (preferred_type == comment_type_block) {
2661 			if (block == COMMENT_INDEX_NONE) {
2662 				return &g_array_index(btv->bflang->st->comments, Tcomment, line);
2663 			} else if (block == COMMENT_INDEX_INHERIT) {
2664 				continue;
2665 			} else {
2666 				return &g_array_index(btv->bflang->st->comments, Tcomment, block);
2667 			}
2668 		} else {
2669 			if (line == COMMENT_INDEX_NONE) {
2670 				return &g_array_index(btv->bflang->st->comments, Tcomment, block);
2671 			} else if (line == COMMENT_INDEX_INHERIT) {
2672 				continue;
2673 			} else {
2674 				return &g_array_index(btv->bflang->st->comments, Tcomment, line);
2675 			}
2676 		}
2677 	}
2678 	return NULL;
2679 }
2680 
2681 
bluefish_text_view_get_auto_complete(BluefishTextView * btv)2682 gboolean bluefish_text_view_get_auto_complete(BluefishTextView * btv)
2683 {
2684 	return (BLUEFISH_TEXT_VIEW(btv->master)->auto_complete);
2685 }
2686 
bluefish_text_view_set_auto_complete(BluefishTextView * btv,gboolean enable)2687 void bluefish_text_view_set_auto_complete(BluefishTextView * btv, gboolean enable)
2688 {
2689 	g_return_if_fail(btv != NULL);
2690 
2691 	if (enable == BLUEFISH_TEXT_VIEW(btv->master)->auto_complete) {
2692 		return;
2693 	}
2694 
2695 	BLUEFISH_TEXT_VIEW(btv->master)->auto_complete = enable;
2696 }
2697 
bluefish_text_view_get_auto_indent(BluefishTextView * btv)2698 gboolean bluefish_text_view_get_auto_indent(BluefishTextView * btv)
2699 {
2700 	return (BLUEFISH_TEXT_VIEW(btv->master)->auto_indent);
2701 }
2702 
bluefish_text_view_set_auto_indent(BluefishTextView * btv,gboolean enable)2703 void bluefish_text_view_set_auto_indent(BluefishTextView * btv, gboolean enable)
2704 {
2705 	g_return_if_fail(btv != NULL);
2706 
2707 	if (enable == BLUEFISH_TEXT_VIEW(btv->master)->auto_indent) {
2708 		return;
2709 	}
2710 
2711 	BLUEFISH_TEXT_VIEW(btv->master)->auto_indent = enable;
2712 }
2713 
bftextview2_parse_static_colors(void)2714 static void bftextview2_parse_static_colors(void)
2715 {
2716 #if GTK_CHECK_VERSION(3,0,0)
2717 	if (!(main_v->props.btv_color_str[BTV_COLOR_CURRENT_LINE]
2718 		  && gdk_rgba_parse(&st_cline_color, main_v->props.btv_color_str[BTV_COLOR_CURRENT_LINE]))) {
2719 		gdk_rgba_parse(&st_cline_color, "#e0e0e0");
2720 	}
2721 	if (!(main_v->props.btv_color_str[BTV_COLOR_WHITESPACE]
2722 		  && gdk_rgba_parse(&st_whitespace_color, main_v->props.btv_color_str[BTV_COLOR_WHITESPACE]))) {
2723 		gdk_rgba_parse(&st_whitespace_color, "#ff0000");
2724 	}
2725 	if (!(main_v->props.btv_color_str[BTV_COLOR_CURSOR_HIGHLIGHT]
2726 		  && gdk_rgba_parse(&st_cursor_highlight_color,
2727 							main_v->props.btv_color_str[BTV_COLOR_CURSOR_HIGHLIGHT]))) {
2728 		gdk_rgba_parse(&st_cursor_highlight_color, "#ffff33");
2729 	}
2730 	if (!(main_v->props.btv_color_str[BTV_COLOR_MARGIN_FG]
2731 		  && gdk_rgba_parse(&st_margin_fg_color, main_v->props.btv_color_str[BTV_COLOR_MARGIN_FG]))) {
2732 		gdk_rgba_parse(&st_margin_fg_color, "#000000");
2733 	}
2734 	if (!(main_v->props.btv_color_str[BTV_COLOR_MARGIN_BG]
2735 		  && gdk_rgba_parse(&st_margin_bg_color, main_v->props.btv_color_str[BTV_COLOR_MARGIN_BG]))) {
2736 		gdk_rgba_parse(&st_margin_bg_color, "#dddddd");
2737 	}
2738 #else
2739 	GString *str;
2740 
2741 	if (!(main_v->props.btv_color_str[BTV_COLOR_CURRENT_LINE]
2742 		  && gdk_color_parse(main_v->props.btv_color_str[BTV_COLOR_CURRENT_LINE], &st_cline_color))) {
2743 		gdk_color_parse("#e0e0e0", &st_cline_color);
2744 	}
2745 	if (!(main_v->props.btv_color_str[BTV_COLOR_WHITESPACE]
2746 		  && gdk_color_parse(main_v->props.btv_color_str[BTV_COLOR_WHITESPACE], &st_whitespace_color))) {
2747 		gdk_color_parse("#ff0000", &st_whitespace_color);
2748 	}
2749 	if (!(main_v->props.btv_color_str[BTV_COLOR_CURSOR_HIGHLIGHT]
2750 		  && gdk_color_parse(main_v->props.btv_color_str[BTV_COLOR_CURSOR_HIGHLIGHT],
2751 							 &st_cursor_highlight_color))) {
2752 		gdk_color_parse("#ffff33", &st_cursor_highlight_color);
2753 	}
2754 	if (!(main_v->props.btv_color_str[BTV_COLOR_MARGIN_FG]
2755 		  && gdk_color_parse(main_v->props.btv_color_str[BTV_COLOR_MARGIN_FG], &st_margin_fg_color))) {
2756 		gdk_color_parse("#000000", &st_margin_fg_color);
2757 	}
2758 	if (!(main_v->props.btv_color_str[BTV_COLOR_MARGIN_BG]
2759 		  && gdk_color_parse(main_v->props.btv_color_str[BTV_COLOR_MARGIN_BG], &st_margin_bg_color))) {
2760 		gdk_color_parse("#000000", &st_margin_bg_color);
2761 	}
2762 
2763 	str = g_string_new("style \"bluefish-cursor\" {");
2764 	if (!main_v->props.use_system_colors && main_v->props.btv_color_str[BTV_COLOR_CURSOR] != NULL
2765 		&& main_v->props.btv_color_str[BTV_COLOR_CURSOR][0] != '\0') {
2766 		g_string_append_printf(str, " GtkTextView::cursor-color = \"%s\"",
2767 							   main_v->props.btv_color_str[BTV_COLOR_CURSOR]);
2768 	}
2769 	g_string_append_printf(str,
2770 						   " GtkWidget::cursor-aspect-ratio = %f }class \"GtkTextView\" style \"bluefish-cursor\"",
2771 						   ((gfloat) (main_v->props.cursor_size) / 100.0));
2772 	gtk_rc_parse_string(str->str);
2773 	g_string_free(str, TRUE);
2774 #endif
2775 }
2776 
bftextview2_init_globals(void)2777 void bftextview2_init_globals(void)
2778 {
2779 	DBG_MSG("sizeof(Tfound)=%ld, sizeof(Tfoundcontext)=%ld,sizeof(Tfoundblock)=%ld\n", (glong) sizeof(Tfound),
2780 			(glong) sizeof(Tfoundcontext), (glong) sizeof(Tfoundblock));
2781 	bftextview2_parse_static_colors();
2782 	if (main_v->props.autocomp_accel_string && main_v->props.autocomp_accel_string[0] != '\0') {
2783 		gtk_accelerator_parse(main_v->props.autocomp_accel_string, &main_v->autocomp_accel_key,
2784 							  &main_v->autocomp_accel_mods);
2785 		if (gtk_accelerator_valid(main_v->autocomp_accel_key, main_v->autocomp_accel_mods)) {
2786 			return;
2787 		}
2788 		g_warning("%s is not a valid shortcut key combination\n", main_v->props.autocomp_accel_string);
2789 	}
2790 	main_v->autocomp_accel_key = ' ';
2791 	main_v->autocomp_accel_mods = GDK_CONTROL_MASK;
2792 }
2793 
bluefish_text_view_set_colors(BluefishTextView * btv,gchar * const * colors)2794 void bluefish_text_view_set_colors(BluefishTextView * btv, gchar * const *colors)
2795 {
2796 	gchar *curlocale = g_strdup(setlocale(LC_NUMERIC, NULL));
2797 	setlocale(LC_NUMERIC, "C");
2798 #if GTK_CHECK_VERSION(3,0,0)
2799 	GdkRGBA color;
2800 	GString *str = g_string_new("");
2801 #if GTK_CHECK_VERSION(3,20,0)
2802 	g_string_append_printf(str, "BluefishTextView {-gtk-cursor-aspect-ratio: %f;}",
2803 						   (gfloat) (main_v->props.cursor_size / 100.0));
2804 #else							/* GTK_CHECK_VERSION(3,20,0) */
2805 	/* in 3.18 I found that this works: "BluefishTextView {-GtkWidget-cursor-aspect-ratio: 0.4;}" */
2806 	g_string_append_printf(str, "BluefishTextView {-GtkWidget-cursor-aspect-ratio: %f;}",
2807 						   (gfloat) (main_v->props.cursor_size / 100.0));
2808 #endif							/* GTK_CHECK_VERSION(3,20,0) */
2809 	if (!main_v->props.use_system_colors) {
2810 #if GTK_CHECK_VERSION(3,20,0)
2811 		if (colors[BTV_COLOR_ED_BG] && colors[BTV_COLOR_ED_BG][0] != '\0') {
2812 			g_string_append_printf(str, "bluefishtextview text {background-color: %s;}",
2813 								   colors[BTV_COLOR_ED_BG]);
2814 		}
2815 		if (colors[BTV_COLOR_SELECTION] && colors[BTV_COLOR_SELECTION][0] != '\0') {
2816 			g_string_append_printf(str,
2817 								   "bluefishtextview selection {background-color: %s;} bluefishtextview selection:focus {background-color: %s;}",
2818 								   colors[BTV_COLOR_SELECTION], colors[BTV_COLOR_SELECTION]);
2819 		}
2820 		if (colors[BTV_COLOR_ED_FG] && gdk_rgba_parse(&color, colors[BTV_COLOR_ED_FG])) {
2821 			g_string_append_printf(str, "bluefishtextview text {color: %s;}", colors[BTV_COLOR_ED_FG]);
2822 		}
2823 		if (colors[BTV_COLOR_CURSOR] && gdk_rgba_parse(&color, colors[BTV_COLOR_CURSOR])) {
2824 			g_string_append_printf(str, "bluefishtextview text {caret-color: %s;}", colors[BTV_COLOR_CURSOR]);
2825 		}
2826 #else							/* GTK_CHECK_VERSION(3,20,0) */
2827 		if (colors[BTV_COLOR_ED_BG] && colors[BTV_COLOR_ED_BG][0] != '\0') {
2828 			g_string_append_printf(str, "BluefishTextView.view {background-color: %s;}",
2829 								   colors[BTV_COLOR_ED_BG]);
2830 		}
2831 		if (colors[BTV_COLOR_SELECTION] && colors[BTV_COLOR_SELECTION][0] != '\0') {
2832 			g_string_append_printf(str,
2833 								   "BluefishTextView.view:selected {background-color: %s;} BluefishTextView.view:selected:focused {background-color: %s;}",
2834 								   colors[BTV_COLOR_SELECTION], colors[BTV_COLOR_SELECTION]);
2835 		}
2836 		if (colors[BTV_COLOR_ED_FG] && gdk_rgba_parse(&color, colors[BTV_COLOR_ED_FG])) {
2837 			gtk_widget_override_color(GTK_WIDGET(btv), GTK_STATE_FLAG_NORMAL /*0x7F */ , &color);
2838 			if (btv->slave)
2839 				gtk_widget_override_color(GTK_WIDGET(btv->slave), GTK_STATE_FLAG_NORMAL /*0x7f */ , &color);
2840 		}
2841 		if (colors[BTV_COLOR_CURSOR] && gdk_rgba_parse(&color, colors[BTV_COLOR_CURSOR])) {
2842 			gtk_widget_override_cursor(GTK_WIDGET(btv), &color, &color);
2843 			if (btv->slave)
2844 				gtk_widget_override_cursor(GTK_WIDGET(btv->slave), &color, &color);
2845 		}
2846 #endif							/* GTK_CHECK_VERSION(3,20,0) */
2847 	}
2848 	if (str->len > 0) {
2849 		GtkStyleContext *stc;
2850 		GtkCssProvider *cssp = gtk_css_provider_new();
2851 		DBG_MSG("gtk >= 3.0.0, about to apply CSS %s\n", str->str);
2852 		gtk_css_provider_load_from_data(cssp, str->str, -1, NULL);
2853 		stc = gtk_widget_get_style_context(GTK_WIDGET(btv));
2854 		gtk_style_context_add_provider(stc, GTK_STYLE_PROVIDER(cssp),
2855 									   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2856 		if (btv->slave) {
2857 			stc = gtk_widget_get_style_context(GTK_WIDGET(btv));
2858 			gtk_style_context_add_provider(stc, GTK_STYLE_PROVIDER(cssp),
2859 										   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2860 		}
2861 		g_object_unref(G_OBJECT(cssp));
2862 	}
2863 	g_string_free(str, TRUE);
2864 #else
2865 	if (!main_v->props.use_system_colors) {
2866 		GdkColor color;
2867 		if (colors[BTV_COLOR_ED_BG] && gdk_color_parse(colors[BTV_COLOR_ED_BG], &color)) {
2868 			gtk_widget_modify_base(GTK_WIDGET(btv), GTK_STATE_NORMAL, &color);
2869 			if (btv->slave)
2870 				gtk_widget_modify_base(GTK_WIDGET(btv->slave), GTK_STATE_NORMAL, &color);
2871 		}
2872 		if (colors[BTV_COLOR_ED_FG] && gdk_color_parse(colors[BTV_COLOR_ED_FG], &color)) {
2873 			gtk_widget_modify_text(GTK_WIDGET(btv), GTK_STATE_NORMAL, &color);
2874 			if (btv->slave)
2875 				gtk_widget_modify_text(GTK_WIDGET(btv->slave), GTK_STATE_NORMAL, &color);
2876 		}
2877 		if (colors[BTV_COLOR_SELECTION] && gdk_color_parse(colors[BTV_COLOR_SELECTION], &color)) {
2878 			gtk_widget_modify_base(GTK_WIDGET(btv), GTK_STATE_SELECTED, &color);
2879 			gtk_widget_modify_base(GTK_WIDGET(btv), GTK_STATE_ACTIVE, &color);
2880 			if (btv->slave) {
2881 				gtk_widget_modify_base(GTK_WIDGET(btv->slave), GTK_STATE_SELECTED, &color);
2882 				gtk_widget_modify_base(GTK_WIDGET(btv->slave), GTK_STATE_ACTIVE, &color);
2883 			}
2884 		}
2885 	}
2886 #endif
2887 	setlocale(LC_NUMERIC, curlocale);
2888 	g_free(curlocale);
2889 }
2890 
bluefish_text_view_select_language(BluefishTextView * btv,const gchar * mime,const gchar * filename)2891 void bluefish_text_view_select_language(BluefishTextView * btv, const gchar * mime, const gchar * filename)
2892 {
2893 	GtkTextIter start, end;
2894 	GtkTextBuffer *buffer;
2895 	BluefishTextView *master = btv->master;
2896 	Tbflang *bflang = langmgr_get_bflang(mime, filename);
2897 
2898 	if (bflang == master->bflang) {
2899 		DBG_MSG("bluefish_text_view_set_mimetype, nothing to do for btv %p\n", btv);
2900 		return;
2901 	}
2902 	buffer = master->buffer;
2903 	/* remove all highlighting */
2904 	cleanup_scanner(master);
2905 	DBG_MSG("bluefish_text_view_set_mimetype, found bflang %p for mimetype %s\n", bflang, mime);
2906 	if (bflang) {
2907 		/* set new language */
2908 		master->bflang = bflang;
2909 		/* restart scanning */
2910 		gtk_text_buffer_get_bounds(buffer, &start, &end);
2911 #ifdef MARKREGION
2912 		markregion_nochange(&master->scanning, gtk_text_iter_get_offset(&start),
2913 							gtk_text_iter_get_offset(&end));
2914 		DBG_MARKREGION("bluefish_text_view_set_mimetype, apply needscanning to %d:%d\n",
2915 					   gtk_text_iter_get_offset(&start), gtk_text_iter_get_offset(&end));
2916 #ifdef HAVE_LIBENCHANT
2917 		markregion_nochange(&master->spellcheck, gtk_text_iter_get_offset(&start),
2918 							gtk_text_iter_get_offset(&end));
2919 #endif
2920 #endif
2921 #ifdef NEEDSCANNING
2922 		gtk_text_buffer_apply_tag(buffer, master->needscanning, &start, &end);
2923 #ifdef HAVE_LIBENCHANT
2924 		gtk_text_buffer_apply_tag(buffer, master->needspellcheck, &start, &end);
2925 #endif
2926 #endif
2927 		btv->needremovetags = 0;
2928 		if (master->enable_scanner) {
2929 			DBG_MSG("bluefish_text_view_select_language, schedule scanning\n");
2930 			bftextview2_schedule_scanning(master);
2931 		}
2932 	} else {
2933 		master->bflang = NULL;
2934 	}
2935 	DBG_MSG("bluefish_text_view_set_mimetype, done for btv %p\n", btv);
2936 }
2937 
bluefish_text_view_get_show_blocks(BluefishTextView * btv)2938 gboolean bluefish_text_view_get_show_blocks(BluefishTextView * btv)
2939 {
2940 	return (btv->show_blocks);
2941 }
2942 
bluefish_text_view_set_show_blocks(BluefishTextView * btv,gboolean show)2943 void bluefish_text_view_set_show_blocks(BluefishTextView * btv, gboolean show)
2944 {
2945 	g_return_if_fail(btv != NULL);
2946 
2947 	if (show == btv->show_blocks) {
2948 		return;
2949 	}
2950 
2951 	btv->show_blocks = show;
2952 	bftextview2_set_margin_size(btv);
2953 	gtk_widget_queue_draw(GTK_WIDGET(btv));
2954 	if (btv->slave)
2955 		gtk_widget_queue_draw(GTK_WIDGET(btv->slave));
2956 }
2957 
bluefish_text_view_set_show_symbols_redraw(BluefishTextView * btv,gboolean show)2958 void bluefish_text_view_set_show_symbols_redraw(BluefishTextView * btv, gboolean show)
2959 {
2960 	g_return_if_fail(btv != NULL);
2961 
2962 	if (show != btv->showsymbols) {
2963 		btv->showsymbols = show;
2964 		bftextview2_set_margin_size(btv);
2965 	}
2966 	gtk_widget_queue_draw(GTK_WIDGET(btv));
2967 	if (btv->slave)
2968 		gtk_widget_queue_draw(GTK_WIDGET(btv->slave));
2969 }
2970 
bluefish_text_view_get_show_line_numbers(BluefishTextView * btv)2971 gboolean bluefish_text_view_get_show_line_numbers(BluefishTextView * btv)
2972 {
2973 	return (btv->show_line_numbers);
2974 }
2975 
bluefish_text_view_set_show_line_numbers(BluefishTextView * btv,gboolean show)2976 void bluefish_text_view_set_show_line_numbers(BluefishTextView * btv, gboolean show)
2977 {
2978 	g_return_if_fail(btv != NULL);
2979 
2980 	if (show == btv->show_line_numbers) {
2981 		return;
2982 	}
2983 
2984 	btv->show_line_numbers = show;
2985 	bftextview2_set_margin_size(btv);
2986 	gtk_widget_queue_draw(GTK_WIDGET(btv));
2987 	if (btv->slave)
2988 		gtk_widget_queue_draw(GTK_WIDGET(btv->slave));
2989 }
2990 
bluefish_text_view_get_show_visible_spacing(BluefishTextView * btv)2991 gboolean bluefish_text_view_get_show_visible_spacing(BluefishTextView * btv)
2992 {
2993 	return (btv->visible_spacing);
2994 }
2995 
bluefish_text_view_set_show_visible_spacing(BluefishTextView * btv,gboolean show)2996 void bluefish_text_view_set_show_visible_spacing(BluefishTextView * btv, gboolean show)
2997 {
2998 	g_return_if_fail(btv != NULL);
2999 
3000 	if (show == btv->visible_spacing) {
3001 		return;
3002 	}
3003 
3004 	btv->visible_spacing = show;
3005 	gtk_widget_queue_draw(GTK_WIDGET(btv));
3006 	if (btv->slave)
3007 		gtk_widget_queue_draw(GTK_WIDGET(btv->slave));
3008 }
3009 
bluefish_text_view_get_show_right_margin(BluefishTextView * btv)3010 gboolean bluefish_text_view_get_show_right_margin(BluefishTextView * btv)
3011 {
3012 	return (btv->show_right_margin);
3013 }
3014 
bluefish_text_view_set_show_right_margin(BluefishTextView * btv,gboolean show)3015 void bluefish_text_view_set_show_right_margin(BluefishTextView * btv, gboolean show)
3016 {
3017 	g_return_if_fail(btv != NULL);
3018 
3019 	if (show == btv->show_right_margin) {
3020 		return;
3021 	}
3022 
3023 	btv->show_right_margin = show;
3024 	gtk_widget_queue_draw(GTK_WIDGET(btv));
3025 	if (btv->slave)
3026 		gtk_widget_queue_draw(GTK_WIDGET(btv->slave));
3027 }
3028 
bluefish_text_view_set_font(BluefishTextView * btv,PangoFontDescription * font_desc)3029 void bluefish_text_view_set_font(BluefishTextView * btv, PangoFontDescription * font_desc)
3030 {
3031 	gtk_widget_modify_font(GTK_WIDGET(btv), font_desc);
3032 	if (btv->slave)
3033 		gtk_widget_modify_font(btv->slave, font_desc);
3034 	btv->margin_pixels_per_char = 0;
3035 	bftextview2_set_margin_size(btv);
3036 }
3037 
bluefish_text_view_get_show_mbhl(BluefishTextView * btv)3038 gboolean bluefish_text_view_get_show_mbhl(BluefishTextView * btv)
3039 {
3040 	return (btv->show_mbhl);
3041 }
3042 
bluefish_text_view_set_show_mbhl(BluefishTextView * btv,gboolean show)3043 void bluefish_text_view_set_show_mbhl(BluefishTextView * btv, gboolean show)
3044 {
3045 	g_return_if_fail(btv != NULL);
3046 
3047 	if (show == btv->show_mbhl) {
3048 		return;
3049 	}
3050 	btv->show_mbhl = show;
3051 	if (!show && btv->showing_blockmatch) {
3052 		GtkTextIter it1, it2;
3053 		gtk_text_buffer_get_bounds(btv->buffer, &it1, &it2);
3054 		gtk_text_buffer_remove_tag(btv->buffer, BLUEFISH_TEXT_VIEW(btv)->blockmatch, &it1, &it2);
3055 		btv->showing_blockmatch = FALSE;
3056 	}
3057 	gtk_widget_queue_draw(GTK_WIDGET(btv));
3058 	if (btv->slave)
3059 		gtk_widget_queue_draw(GTK_WIDGET(btv->slave));
3060 }
3061 
3062 #ifdef HAVE_LIBENCHANT
bluefish_text_view_set_spell_check(BluefishTextView * btv,gboolean spell_check)3063 void bluefish_text_view_set_spell_check(BluefishTextView * btv, gboolean spell_check)
3064 {
3065 	GtkTextIter start, end;
3066 	BluefishTextView *master = btv->master;
3067 
3068 	g_return_if_fail(btv != NULL);
3069 
3070 	if (master->spell_check == spell_check) {
3071 		return;
3072 	}
3073 
3074 	master->spell_check = spell_check;
3075 	gtk_text_buffer_get_bounds(master->buffer, &start, &end);
3076 
3077 	if (master->spell_check) {
3078 #ifdef NEEDSCANNING
3079 		gtk_text_buffer_apply_tag(master->buffer, master->needspellcheck, &start, &end);
3080 #endif
3081 #ifdef MARKREGION
3082 		markregion_nochange(&master->spellcheck, 0, gtk_text_iter_get_offset(&end));
3083 #endif
3084 		bftextview2_schedule_scanning(master);
3085 	} else {
3086 		gtk_text_buffer_remove_tag_by_name(master->buffer, "_spellerror_", &start, &end);
3087 	}
3088 }
3089 #endif
3090 
3091 static gboolean
bluefish_text_view_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip)3092 bluefish_text_view_query_tooltip(GtkWidget * widget, gint x, gint y, gboolean keyboard_tip,
3093 								 GtkTooltip * tooltip)
3094 {
3095 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
3096 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
3097 
3098 	if (!keyboard_tip
3099 		&& x < (master->margin_pixels_chars + master->margin_pixels_block + master->margin_pixels_symbol)) {
3100 		gint bx, by, trailing;
3101 		GtkTextIter iter;
3102 		gchar *str;
3103 		gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_LEFT, x, y, &bx, &by);
3104 		/*g_print("bluefish_text_view_query_tooltip, get bookmark popup for %d:%d got buffer coordinates %d:%d\n",x,y,bx,by); */
3105 		gtk_text_view_get_iter_at_position(GTK_TEXT_VIEW(btv), &iter, &trailing, MIN(bx, 0), by);
3106 		str = bmark_get_tooltip_for_line(master->doc, gtk_text_iter_get_line(&iter));
3107 		if (str) {
3108 			gtk_tooltip_set_markup(tooltip, str);
3109 			g_free(str);
3110 			return TRUE;
3111 		}
3112 		return FALSE;
3113 	}
3114 
3115 	if (master->bflang && master->bflang->st && master->enable_scanner && master->scanner_idle == 0
3116 		&& main_v->props.show_tooltip_reference) {
3117 		GtkTextIter iter, mstart;
3118 		GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(master));
3119 		gint contextnum;
3120 		guint matchnum;
3121 		/* get position */
3122 		if (keyboard_tip) {
3123 			gint offset;
3124 			g_object_get(buffer, "cursor-position", &offset, NULL);
3125 			gtk_text_buffer_get_iter_at_offset(buffer, &iter, offset);
3126 		} else {
3127 			gint bx, by, trailing;
3128 			/*g_print("get iter at mouse position x=%d-margin=%d y=%d\n",x,x - (btv->margin_pixels_chars + btv->margin_pixels_block +
3129 			   btv->margin_pixels_symbol),y); */
3130 			gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT,
3131 												  x - (btv->margin_pixels_chars + btv->margin_pixels_block +
3132 													   btv->margin_pixels_symbol), y, &bx, &by);
3133 			/*g_print("bluefish_text_view_query_tooltip, get reference popup for %d:%d got buffer coordinates %d:%d\n",x,y,bx,by); */
3134 			if (bx < 0)
3135 				return FALSE;
3136 			/* if I don't do this check, I get the following error during 'Collapse all'
3137 			   (bluefish-unstable:3866): Gtk-WARNING **: /build/buildd/gtk+2.0-2.16.1/gtk/gtktextbtree.c:4017: byte index off the end of the line
3138 			   Gtk-ERROR **: Byte index 533 is off the end of the line aborting...
3139 
3140 			   #0  0xb80bf430 in __kernel_vsyscall ()
3141 			   #1  0xb761f6d0 in raise () from /lib/tls/i686/cmov/libc.so.6
3142 			   #2  0xb7621098 in abort () from /lib/tls/i686/cmov/libc.so.6
3143 			   #3  0xb77b3eac in IA__g_logv (log_domain=0xb7fcba77 "Gtk",
3144 			   log_level=G_LOG_LEVEL_ERROR,
3145 			   format=0xb8076f74 "Byte index %d is off the end of the line",
3146 			   args1=0xbfcdc06c "\206\002")
3147 			   at /build/buildd/glib2.0-2.20.1/glib/gmessages.c:506
3148 			   #4  0xb77b3ee6 in IA__g_log (log_domain=0xb7fcba77 "Gtk",
3149 			   log_level=G_LOG_LEVEL_ERROR,
3150 			   format=0xb8076f74 "Byte index %d is off the end of the line")
3151 			   at /build/buildd/glib2.0-2.20.1/glib/gmessages.c:526
3152 			   #5  0xb7ed4ee7 in iter_set_from_byte_offset (iter=0xbfcdc0c4, line=0x9b1cbb8,
3153 			   byte_offset=646) at /build/buildd/gtk+2.0-2.16.1/gtk/gtktextiter.c:110
3154 			   #6  0xb7ed967f in IA__gtk_text_iter_set_visible_line_index (iter=0xbfcdc1e4,
3155 			   byte_on_line=4) at /build/buildd/gtk+2.0-2.16.1/gtk/gtktextiter.c:3906
3156 			   #7  0xb7edc8c0 in line_display_index_to_iter (layout=0x9711728,
3157 			   display=0x9c30618, iter=0xbfcdc1e4, index=69, trailing=0)
3158 			   at /build/buildd/gtk+2.0-2.16.1/gtk/gtktextlayout.c:2549
3159 			   #8  0xb7ee099c in IA__gtk_text_layout_get_iter_at_position (layout=0x9711728,
3160 			   target_iter=0xbfcdc1e4, trailing=0xbfcdc21c, x=-5, y=<value optimized out>)
3161 			   at /build/buildd/gtk+2.0-2.16.1/gtk/gtktextlayout.c:2670
3162 			   #9  0x080646cd in bluefish_text_view_query_tooltip (widget=0x9783050, x=47,
3163 			   y=88, keyboard_tip=0, tooltip=0x97d0a38) at bftextview2.c:1187
3164 
3165 			   I guess this is a race condition, during collapse all a lot of text is made hidden
3166 			   and for the tooltip we request an iter somewhere in the text that is becoming hidden
3167 			 */
3168 			/*g_print("get iter at buffer position bx=%d by=%d\n",bx,by); */
3169 			/* gtk 2.14 cannot handle a NULL instead of &trailing. so although the docs tell
3170 			   that if you don't need it you can pass NULL, we will not do so. */
3171 			gtk_text_view_get_iter_at_position(GTK_TEXT_VIEW(btv), &iter, &trailing, bx, by);
3172 			/*g_print("done\n"); */
3173 		}
3174 		mstart = iter;
3175 		gtk_text_iter_set_line_offset(&mstart, 0);
3176 		gtk_text_iter_forward_char(&iter);
3177 		DBG_TOOLTIP("scan for tooltip: start at %d, position=%d...\n", gtk_text_iter_get_offset(&mstart),
3178 					gtk_text_iter_get_offset(&iter));
3179 		matchnum = scan_for_identifier_at_position(master, &mstart, &iter, &contextnum, NULL, NULL);
3180 		if (matchnum) {
3181 			if (g_array_index(master->bflang->st->matches, Tpattern, matchnum).reference) {
3182 				gtk_tooltip_set_markup(tooltip,
3183 									   g_array_index(master->bflang->st->matches, Tpattern,
3184 													 matchnum).reference);
3185 				return TRUE;
3186 			}
3187 
3188 
3189 
3190 /*			DBG_TOOLTIP("we have a match in context %d, has_patternhash=%d\n", contextnum,
3191 						(g_array_index(master->bflang->st->contexts, Tcontext, contextnum).patternhash !=
3192 						 NULL));
3193 			if (g_array_index(master->bflang->st->contexts, Tcontext, contextnum).patternhash) {
3194 				gint pattern_id;
3195 				gchar *key = gtk_text_buffer_get_text(buffer, &mstart, &iter, TRUE);
3196 				g_print("lookup reference for %s\n",key);
3197 				pattern_id =
3198 					GPOINTER_TO_INT(g_hash_table_lookup
3199 									(g_array_index
3200 									 (master->bflang->st->contexts, Tcontext, contextnum).patternhash, key));
3201 				if (pattern_id && g_array_index(master->bflang->st->matches, Tpattern, pattern_id).reference) {
3202 					DBG_TOOLTIP("key=%s, value=%s\n", key,
3203 								g_array_index(master->bflang->st->matches, Tpattern, pattern_id).reference);
3204 					gtk_tooltip_set_markup(tooltip,
3205 										   g_array_index(master->bflang->st->matches, Tpattern,
3206 														 pattern_id).reference);
3207 					g_free(key);
3208 					return TRUE;
3209 				}
3210 				g_free(key);
3211 			}*/
3212 		}
3213 	}
3214 
3215 	return FALSE;
3216 }
3217 
bluefish_text_view_focus_out_event(GtkWidget * widget,GdkEventFocus * event)3218 static gboolean bluefish_text_view_focus_out_event(GtkWidget * widget, GdkEventFocus * event)
3219 {
3220 	if (BLUEFISH_TEXT_VIEW(widget)->autocomp) {
3221 		autocomp_stop(BLUEFISH_TEXT_VIEW(widget));
3222 	}
3223 	return GTK_WIDGET_CLASS(bluefish_text_view_parent_class)->focus_out_event(widget, event);
3224 }
3225 
3226 #if GTK_CHECK_VERSION(3,16,0)
3227 
3228 #define bf_text_iter_char_in_string(iter, string) (string?strchr(string, gtk_text_iter_get_char(iter)):FALSE)
3229 
bf_gtk_text_iter_forward_visible_word_end(GtkTextIter * iter,const gchar * smartselectionchars)3230 static gboolean bf_gtk_text_iter_forward_visible_word_end(GtkTextIter * iter, const gchar *smartselectionchars)
3231 {
3232 	gboolean ret=TRUE;
3233 	DBG_MSG("bf_gtk_text_iter_forward_visible_word_start, iter=%c ends_word=%d\n",gtk_text_iter_get_char(iter),gtk_text_iter_starts_word(iter));
3234 	if (!gtk_text_iter_ends_word(iter) && gtk_text_iter_inside_word(iter)) {
3235 		ret = gtk_text_iter_forward_visible_word_end(iter);
3236 		DBG_MSG("bf_gtk_text_iter_forward_visible_word_start, iter=%c\n",gtk_text_iter_get_char(iter));
3237 	}
3238 	while (!gtk_text_iter_is_end(iter)) {
3239 		if (bf_text_iter_char_in_string(iter, smartselectionchars)) {
3240 			gtk_text_iter_forward_char(iter);
3241 		} else if (gtk_text_iter_starts_word(iter)) {
3242 			ret = gtk_text_iter_forward_visible_word_end(iter);
3243 		} else {
3244 			break;
3245 		}
3246 	}
3247 	return ret;
3248 }
bf_gtk_text_iter_starts_word(GtkTextIter * iter,const gchar * smartselectionchars)3249 static gboolean bf_gtk_text_iter_starts_word(GtkTextIter * iter, const gchar *smartselectionchars)
3250 {
3251 	GtkTextIter before = *iter;
3252 	if (bf_text_iter_char_in_string(iter, smartselectionchars)
3253 		|| (gtk_text_iter_backward_char(&before) && bf_text_iter_char_in_string(&before, smartselectionchars))) {
3254 		DBG_MSG("starts: before has %c, return FALSE\n", gtk_text_iter_get_char(&before));
3255 		return FALSE;
3256 	}
3257 	return gtk_text_iter_starts_word(iter);
3258 }
3259 
bf_gtk_text_iter_backward_visible_word_start(GtkTextIter * iter,const gchar * smartselectionchars)3260 static gboolean bf_gtk_text_iter_backward_visible_word_start(GtkTextIter * iter, const gchar *smartselectionchars)
3261 {
3262 	gboolean ret=TRUE;
3263 	GtkTextIter before;
3264 	DBG_MSG("bf_gtk_text_iter_backward_visible_word_start, iter=%c ends_word=%d\n",gtk_text_iter_get_char(iter),gtk_text_iter_starts_word(iter));
3265 	if (!gtk_text_iter_starts_word(iter) && gtk_text_iter_inside_word(iter)) {
3266 		ret = gtk_text_iter_backward_visible_word_start(iter);
3267 		DBG_MSG("bf_gtk_text_iter_backward_visible_word_start, iter=%c\n",gtk_text_iter_get_char(iter));
3268 	}
3269 	while (!gtk_text_iter_is_start(iter)) {
3270 		before = *iter;
3271 		gtk_text_iter_backward_char(&before);
3272 		DBG_MSG("bf_gtk_text_iter_backward_visible_word_start, in loop, iter=%c, before=%c\n",gtk_text_iter_get_char(iter),gtk_text_iter_get_char(&before));
3273 		if (bf_text_iter_char_in_string(&before, smartselectionchars)) {
3274 			/* one step back */
3275 			*iter = before;
3276 		} else if (gtk_text_iter_ends_word(iter)) {
3277 			ret = gtk_text_iter_backward_visible_word_start(iter);
3278 		} else {
3279 			break;
3280 		}
3281 	}
3282 	return ret;
3283 }
3284 
3285 
bf_gtk_text_iter_ends_word(GtkTextIter * iter,const gchar * smartselectionchars)3286 static gboolean bf_gtk_text_iter_ends_word(GtkTextIter * iter, const gchar *smartselectionchars)
3287 {
3288 	if (bf_text_iter_char_in_string(iter, smartselectionchars)) {
3289 		DBG_MSG("ends: iter has %c, return FALSE\n", gtk_text_iter_get_char(iter));
3290 		return FALSE;
3291 	}
3292 	return gtk_text_iter_ends_word(iter);
3293 }
3294 
bf_gtk_text_iter_inside_word(GtkTextIter * iter,const gchar * smartselectionchars)3295 static gboolean bf_gtk_text_iter_inside_word(GtkTextIter * iter, const gchar *smartselectionchars)
3296 {
3297 	if (bf_text_iter_char_in_string(iter, smartselectionchars)) {
3298 		DBG_MSG("inside: iter has %c, return TRUE\n", gtk_text_iter_get_char(iter));
3299 		return TRUE;
3300 	}
3301 	return gtk_text_iter_inside_word(iter);
3302 }
3303 
3304 static gboolean
bluefish_text_view_extend_selection(GtkTextView * widget,GtkTextExtendSelection granularity,const GtkTextIter * location,GtkTextIter * start,GtkTextIter * end)3305 bluefish_text_view_extend_selection(GtkTextView * widget, GtkTextExtendSelection granularity,
3306 									const GtkTextIter * location, GtkTextIter * start, GtkTextIter * end)
3307 {
3308 	if (granularity != GTK_TEXT_EXTEND_SELECTION_WORD) {
3309 		/* ignore line or character selection */
3310 		return GDK_EVENT_PROPAGATE;
3311 	}
3312 	BluefishTextView *btv = BLUEFISH_TEXT_VIEW(widget);
3313 	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
3314 	gchar *smartselectionchars;
3315 	DBG_MSG("\n\nbluefish_text_view_extend_selection, started, location at %d\n",
3316 			gtk_text_iter_get_offset(location));
3317 	smartselectionchars = master->bflang ? master->bflang->smartselectionchars:NULL;
3318 
3319 	*start = *end = *location;
3320 	if (bf_gtk_text_iter_inside_word(start, smartselectionchars)) {
3321 		DBG_MSG("inside word\n");
3322 		if (!bf_gtk_text_iter_starts_word(start, smartselectionchars))
3323 			bf_gtk_text_iter_backward_visible_word_start(start, smartselectionchars);
3324 		DBG_MSG("bluefish_text_view_extend_selection, started, start is at %d\n",gtk_text_iter_get_offset(start));
3325 		if (!bf_gtk_text_iter_ends_word(end, smartselectionchars)) {
3326 			if (!bf_gtk_text_iter_forward_visible_word_end(end, smartselectionchars))
3327 				gtk_text_iter_forward_to_end(end);
3328 			DBG_MSG("bluefish_text_view_extend_selection, started, end is at %d\n",gtk_text_iter_get_offset(end));
3329 		}
3330 	} else {
3331 		GtkTextIter tmp;
3332 		DBG_MSG("not inside word\n");
3333 		tmp = *start;
3334 		if (bf_gtk_text_iter_backward_visible_word_start(&tmp, smartselectionchars))
3335 			bf_gtk_text_iter_forward_visible_word_end(&tmp, smartselectionchars);
3336 
3337 		if (gtk_text_iter_get_line(&tmp) == gtk_text_iter_get_line(start))
3338 			*start = tmp;
3339 		else
3340 			gtk_text_iter_set_line_offset(start, 0);
3341 
3342 		tmp = *end;
3343 		if (!bf_gtk_text_iter_forward_visible_word_end(&tmp, smartselectionchars))
3344 			gtk_text_iter_forward_to_end(&tmp);
3345 
3346 		if (gtk_text_iter_ends_word(&tmp))
3347 			bf_gtk_text_iter_backward_visible_word_start(&tmp, smartselectionchars);
3348 
3349 		if (gtk_text_iter_get_line(&tmp) == gtk_text_iter_get_line(end))
3350 			*end = tmp;
3351 		else
3352 			gtk_text_iter_forward_to_line_end(end);
3353 	}
3354 	/*g_print("bluefish_text_view_extend_selection, started, location=%d, start=%d,end=%d\n",
3355 			gtk_text_iter_get_offset(location), gtk_text_iter_get_offset(start),
3356 			gtk_text_iter_get_offset(end));*/
3357 	return GDK_EVENT_STOP;
3358 }
3359 #endif /*GTK_CHECK_VERSION(3,16,0) for expand-selection signal */
3360 
bluefish_text_view_finalize(GObject * object)3361 static void bluefish_text_view_finalize(GObject * object)
3362 {
3363 	BluefishTextView *btv;
3364 
3365 	g_return_if_fail(object != NULL);
3366 	btv = BLUEFISH_TEXT_VIEW(object);
3367 	DEBUG_MSG("bluefish_text_view_finalize, destroy BluefishTextView btv=%p, btv->slave=%p, btv->master=%p\n",
3368 			  btv, btv->slave, btv->master);
3369 	if (btv->master != btv && BLUEFISH_TEXT_VIEW(btv->master)->slave == btv) {
3370 		BLUEFISH_TEXT_VIEW(btv->master)->slave = NULL;
3371 	}
3372 
3373 	if (btv->scanner_delayed) {
3374 		g_source_remove(btv->scanner_delayed);
3375 		btv->scanner_delayed = 0;
3376 	}
3377 	if (btv->scanner_idle) {
3378 		g_source_remove(btv->scanner_idle);
3379 		btv->scanner_idle = 0;
3380 	}
3381 	if (btv->user_idle) {
3382 		g_source_remove(btv->user_idle);
3383 		btv->user_idle = 0;
3384 	}
3385 	if (btv->mark_set_idle) {
3386 		g_source_remove(btv->mark_set_idle);
3387 		btv->mark_set_idle = 0;
3388 	}
3389 	if (btv->autocomp) {
3390 		autocomp_stop(btv);
3391 	}
3392 	if (btv->scancache.foundcaches) {
3393 		scancache_destroy(btv);
3394 	}
3395 	if (btv->user_idle_timer) {
3396 		g_timer_destroy(btv->user_idle_timer);
3397 		btv->user_idle_timer = NULL;
3398 	}
3399 	if (btv->buffer) {
3400 		DEBUG_MSG("bluefish_text_view_finalize %p, disconnect signals from buffer %p\n", btv, btv->buffer);
3401 		if (btv->insert_text_id)
3402 			g_signal_handler_disconnect(btv->buffer, btv->insert_text_id);
3403 		if (btv->insert_text_after_id)
3404 			g_signal_handler_disconnect(btv->buffer, btv->insert_text_after_id);
3405 		if (btv->mark_set_id)
3406 			g_signal_handler_disconnect(btv->buffer, btv->mark_set_id);
3407 		if (btv->delete_range_id)
3408 			g_signal_handler_disconnect(btv->buffer, btv->delete_range_id);
3409 		if (btv->delete_range_after_id)
3410 			g_signal_handler_disconnect(btv->buffer, btv->delete_range_after_id);
3411 	}
3412 	btv->bflang = NULL;
3413 	btv->enable_scanner = FALSE;
3414 	if (G_OBJECT_CLASS(bluefish_text_view_parent_class)->finalize) {
3415 		DEBUG_MSG("call parent class finalize() on %p\n", object);
3416 		G_OBJECT_CLASS(bluefish_text_view_parent_class)->finalize(object);
3417 	}
3418 }
3419 
3420 /* *************************************************************** */
3421 /* widget stuff below */
3422 /* *************************************************************** */
3423 /*
3424 static void bluefish_text_view_finalize(GObject * object)
3425 {
3426 	G_OBJECT_CLASS(bluefish_text_view_parent_class)->finalize(object);
3427 }*/
3428 /*
3429 static GObject *bluefish_text_view_create(GType type, guint n_construct_properties,
3430 										  GObjectConstructParam * construct_properties)
3431 {
3432 	BluefishTextViewClass *klass =
3433 		BLUEFISH_TEXT_VIEW_CLASS(g_type_class_peek(BLUEFISH_TYPE_TEXT_VIEW));
3434 	GObjectClass *parent_class = G_OBJECT_CLASS(g_type_class_peek_parent(klass));
3435 	GObject *obj = parent_class->constructor(type,
3436 											 n_construct_properties,
3437 											 construct_properties);
3438 
3439 	/ * This constructor is not needed right now * /
3440 
3441 	return (obj);
3442 }*/
3443 
bluefish_text_view_class_init(BluefishTextViewClass * klass)3444 static void bluefish_text_view_class_init(BluefishTextViewClass * klass)
3445 {
3446 	GObjectClass *object_class = G_OBJECT_CLASS(klass);
3447 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
3448 #if GTK_CHECK_VERSION(3,14,0)
3449 	GtkTextViewClass *textview_class = GTK_TEXT_VIEW_CLASS(klass);
3450 #endif
3451 #if GTK_CHECK_VERSION(3,20,0)
3452 	gtk_widget_class_set_css_name(widget_class, "bluefishtextview");
3453 #endif
3454 /*	object_class->constructor = bluefish_text_view_create;*/
3455 	object_class->finalize = bluefish_text_view_finalize;
3456 
3457 	widget_class->button_press_event = bluefish_text_view_button_press_event;
3458 	widget_class->motion_notify_event = bluefish_text_view_motion_notify_event;
3459 	widget_class->button_release_event = bluefish_text_view_button_release_event;
3460 #if GTK_CHECK_VERSION(3,0,0)
3461 	widget_class->draw = bluefish_text_view_draw;
3462 #else
3463 	widget_class->expose_event = bluefish_text_view_expose_event;
3464 #endif							/* gtk3 */
3465 	widget_class->key_press_event = bluefish_text_view_key_press_event;
3466 	widget_class->key_release_event = bluefish_text_view_key_release_event;
3467 	widget_class->query_tooltip = bluefish_text_view_query_tooltip;
3468 	widget_class->focus_out_event = bluefish_text_view_focus_out_event;
3469 #if GTK_CHECK_VERSION(3,14,0)
3470 	textview_class->draw_layer = bluefish_text_view_draw_layer;
3471 #endif
3472 #if GTK_CHECK_VERSION(3,16,0)
3473 	textview_class->extend_selection = bluefish_text_view_extend_selection;
3474 #endif
3475 
3476 }
3477 
3478 void
bluefish_text_view_multiset(BluefishTextView * btv,gpointer doc,gint view_line_numbers,gint view_blocks,gint autoindent,gint autocomplete,gint show_mbhl,gint enable_scanner)3479 bluefish_text_view_multiset(BluefishTextView * btv, gpointer doc, gint view_line_numbers,
3480 							gint view_blocks, gint autoindent, gint autocomplete, gint show_mbhl,
3481 							gint enable_scanner)
3482 {
3483 	BLUEFISH_TEXT_VIEW(btv->master)->doc = doc;
3484 	BLUEFISH_TEXT_VIEW(btv->master)->show_line_numbers = view_line_numbers;
3485 	BLUEFISH_TEXT_VIEW(btv->master)->show_blocks = view_blocks;
3486 	BLUEFISH_TEXT_VIEW(btv->master)->auto_indent = autoindent;
3487 	BLUEFISH_TEXT_VIEW(btv->master)->auto_complete = autocomplete;
3488 	BLUEFISH_TEXT_VIEW(btv->master)->show_mbhl = show_mbhl;
3489 	BLUEFISH_TEXT_VIEW(btv->master)->enable_scanner = enable_scanner;
3490 }
3491 
bluefish_text_view_init(BluefishTextView * textview)3492 static void bluefish_text_view_init(BluefishTextView * textview)
3493 {
3494 	GtkTextTagTable *ttt;
3495 /*	PangoFontDescription *font_desc;*/
3496 	textview->user_idle_timer = g_timer_new();
3497 	textview->scancache.foundcaches = g_sequence_new(NULL);
3498 #ifdef UPDATE_OFFSET_DELAYED
3499 	textview->scancache.offsetupdates = NULL;
3500 #endif
3501 	bluefish_text_view_set_colors(textview, main_v->props.btv_color_str);
3502 	textview->showsymbols = FALSE;
3503 	textview->button_press_line = -1;
3504 	ttt = langmgr_get_tagtable();
3505 #ifdef NEEDSCANNING
3506 	textview->needscanning = gtk_text_tag_table_lookup(ttt, "_needscanning_");
3507 #ifdef HAVE_LIBENCHANT
3508 	textview->needspellcheck = gtk_text_tag_table_lookup(ttt, "_needspellcheck_");
3509 #endif							/*HAVE_LIBENCHANT */
3510 #endif
3511 	textview->blockmatch = gtk_text_tag_table_lookup(ttt, "blockmatch");
3512 	textview->cursortag = gtk_text_tag_table_lookup(ttt, "cursor_highlight");
3513 	textview->enable_scanner = FALSE;
3514 	/*font_desc = pango_font_description_from_string("Monospace 10");
3515 	   gtk_widget_modify_font(GTK_WIDGET(textview), font_desc);
3516 	   pango_font_description_free(font_desc); */
3517 }
3518 
bftextview2_new(void)3519 GtkWidget *bftextview2_new(void)
3520 {
3521 	BluefishTextView *textview = g_object_new(BLUEFISH_TYPE_TEXT_VIEW,
3522 											  "has-tooltip", TRUE, NULL);
3523 
3524 	g_return_val_if_fail(textview != NULL, NULL);
3525 	textview->master = textview;
3526 	textview->slave = NULL;
3527 	return GTK_WIDGET(textview);
3528 }
3529 
bftextview2_new_with_buffer(GtkTextBuffer * buffer)3530 GtkWidget *bftextview2_new_with_buffer(GtkTextBuffer * buffer)
3531 {
3532 	BluefishTextView *textview = (BluefishTextView *) bftextview2_new();
3533 
3534 	g_return_val_if_fail(textview != NULL, NULL);
3535 	DBG_MSG("bftextview2_new_with_buffer, textview=%p, buffer=%p\n", textview, buffer);
3536 	gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
3537 	textview->buffer = buffer;
3538 	textview->insert_text_id =
3539 		g_signal_connect(G_OBJECT(buffer), "insert-text", G_CALLBACK(bftextview2_insert_text_lcb), textview);
3540 	textview->insert_text_after_id =
3541 		g_signal_connect_after(G_OBJECT(buffer), "insert-text", G_CALLBACK(bftextview2_insert_text_after_lcb),
3542 							   textview);
3543 	textview->mark_set_id =
3544 		g_signal_connect_after(G_OBJECT(buffer), "mark-set", G_CALLBACK(bftextview2_mark_set_lcb), textview);
3545 	textview->delete_range_id =
3546 		g_signal_connect(G_OBJECT(buffer), "delete-range", G_CALLBACK(bftextview2_delete_range_lcb),
3547 						 textview);
3548 	textview->delete_range_after_id =
3549 		g_signal_connect_after(G_OBJECT(buffer), "delete-range",
3550 							   G_CALLBACK(bftextview2_delete_range_after_lcb), textview);
3551 	return GTK_WIDGET(textview);
3552 }
3553 
bftextview2_new_slave(BluefishTextView * master)3554 GtkWidget *bftextview2_new_slave(BluefishTextView * master)
3555 {
3556 	BluefishTextView *textview;
3557 	GtkTextBuffer *buffer;
3558 
3559 	g_return_val_if_fail(master != NULL, NULL);
3560 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(master));
3561 	DEBUG_MSG("bftextview2_new_slave, master=%p, buffer=%p\n", master, buffer);
3562 	textview = (BluefishTextView *) bftextview2_new_with_buffer(buffer);
3563 	g_return_val_if_fail(textview != NULL, NULL);
3564 
3565 	textview->master = master;
3566 	master->slave = textview;
3567 	DEBUG_MSG("bftextview2_new_slave, created slave %p for master %p\n", textview, master);
3568 	gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(textview), GTK_TEXT_WINDOW_LEFT,
3569 										 BLUEFISH_TEXT_VIEW(master)->margin_pixels_chars
3570 										 + BLUEFISH_TEXT_VIEW(master)->margin_pixels_block
3571 										 + BLUEFISH_TEXT_VIEW(master)->margin_pixels_symbol);
3572 	return GTK_WIDGET(textview);
3573 }
3574