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 >k_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 >k_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 >k_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 >k_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 >k_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 >k_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, ¤tpos, 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(¤tpos, &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