1 /* gbp-omni-gutter-renderer.c
2 *
3 * Copyright 2017-2019 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #define G_LOG_DOMAIN "gbp-omni-gutter-renderer"
22
23 #include "config.h"
24
25 #include <dazzle.h>
26 #include <glib/gi18n.h>
27 #include <string.h>
28
29 #include <libide-core.h>
30 #include <libide-code.h>
31 #include <libide-debugger.h>
32 #include <libide-sourceview.h>
33
34 #include "ide-debugger-private.h"
35
36 #include "gbp-omni-gutter-renderer.h"
37
38 /**
39 * SECTION:gbp-omni-gutter-renderer
40 * @title: GbpOmniGutterRenderer
41 * @short_description: A featureful gutter renderer for the code editor
42 *
43 * This is a #GtkSourceGutterRenderer that knows how to render many of
44 * our components necessary for Builder. Because of the complexity of
45 * Builder, using traditional gutter renderers takes up a great deal
46 * of horizontal space.
47 *
48 * By overlapping some of our components, we can take up less space and
49 * be easier for the user with increased hit-targets.
50 *
51 * Additionally, we can render faster because we can coalesce work.
52 *
53 * Since: 3.32
54 */
55
56 #define ARROW_WIDTH 5
57 #define CHANGE_WIDTH 2
58 #define DELETE_WIDTH 5.0
59 #define DELETE_HEIGHT 8.0
60
61 #define IS_BREAKPOINT(i) ((i)->is_breakpoint || (i)->is_countpoint || (i)->is_watchpoint)
62 #define IS_DIAGNOSTIC(i) ((i)->is_error || (i)->is_warning || (i)->is_note)
63 #define IS_LINE_CHANGE(i) ((i)->is_add || (i)->is_change || \
64 (i)->is_delete || (i)->is_next_delete || (i)->is_prev_delete)
65
66 struct _GbpOmniGutterRenderer
67 {
68 GtkSourceGutterRenderer parent_instance;
69
70 GSettings *settings;
71 gint line_spacing;
72
73 IdeDebuggerBreakpoints *breakpoints;
74
75 GArray *lines;
76
77 DzlSignalGroup *view_signals;
78 DzlSignalGroup *buffer_signals;
79
80 /*
81 * A scaled font description that matches the size of the text
82 * within the source view. Cached to avoid recreating it on ever
83 * frame render.
84 */
85 PangoFontDescription *scaled_font_desc;
86
87 /* TODO: It would be nice to use some basic caching here
88 * so we don't waste 6Kb-12Kb of data on these surfaces.
89 * But that can be done later after this patch set merges.
90 */
91 cairo_surface_t *note_surface;
92 cairo_surface_t *warning_surface;
93 cairo_surface_t *error_surface;
94 cairo_surface_t *note_selected_surface;
95 cairo_surface_t *warning_selected_surface;
96 cairo_surface_t *error_selected_surface;
97
98 /*
99 * We cache various colors we need from the style scheme to avoid
100 * looking them up very often, as it is CPU time consuming. We also
101 * use these colors to prime the symbolic colors for the icon surfaces
102 * to they look appropriate for the style scheme.
103 */
104 struct {
105 GdkRGBA fg;
106 GdkRGBA bg;
107 gboolean bold;
108 } text, current, bkpt, ctpt;
109 GdkRGBA stopped_bg;
110 struct {
111 GdkRGBA add;
112 GdkRGBA remove;
113 GdkRGBA change;
114 } changes;
115
116 /*
117 * We need to reuse a single pango layout while drawing all the lines
118 * to keep the overhead low. We don't have pixel caching on the gutter
119 * data so keeping this stuff fast is critical.
120 */
121 PangoLayout *layout;
122
123 /*
124 * We reuse a simple bold attr list for the current line number
125 * information. This way we don't have to do any pango markup
126 * parsing.
127 */
128 PangoAttrList *bold_attrs;
129
130 /* We stash a copy of how long the line numbers could be. 1000 => 4. */
131 guint n_chars;
132
133 /* While processing the lines, we track what our first line number is
134 * so that differential calculation for each line is cheap by avoiding
135 * accessing GtkTextIter information.
136 */
137 guint begin_line;
138
139 /*
140 * While starting a render, we check to see what the current
141 * breakpoint line is (so we can draw the proper background.
142 *
143 * TODO: Add a callback to the debug manager to avoid querying this
144 * information on every draw cycle.
145 */
146 gint stopped_line;
147
148 /*
149 * To avoid doing multiple line recalculations inline, we defer our
150 * changed handler until we've re-entered teh main loop. Otherwise
151 * we could handle lots of small changes during automated processing
152 * of the underlying buffer.
153 */
154 guint resize_source;
155
156 /*
157 * The number_width field contains the maximum width of the text as
158 * sized by Pango. It is in pixel units in the scale of the widget
159 * as the underlying components will automatically deal with scaling
160 * for us (as necessary).
161 */
162 gint number_width;
163
164 /*
165 * Calculated size for diagnostics, to be a nearest icon-size based
166 * on the height of the line text.
167 */
168 gint diag_size;
169
170 /*
171 * Line that the cursor is on. Used for relative line number rendering.
172 */
173 guint cursor_line;
174
175 /*
176 * Some users might want to toggle off individual features of the
177 * omni gutter, and these boolean properties provide that. Other
178 * components map them to GSettings values to be toggled.
179 */
180 guint show_line_changes : 1;
181 guint show_line_numbers : 1;
182 guint show_relative_line_numbers : 1;
183 guint show_line_diagnostics : 1;
184 };
185
186 enum {
187 FOREGROUND,
188 BACKGROUND,
189 };
190
191 enum {
192 PROP_0,
193 PROP_SHOW_LINE_CHANGES,
194 PROP_SHOW_LINE_NUMBERS,
195 PROP_SHOW_RELATIVE_LINE_NUMBERS,
196 PROP_SHOW_LINE_DIAGNOSTICS,
197 N_PROPS
198 };
199
200 typedef struct
201 {
202 /* The line contains a regular breakpoint */
203 guint is_breakpoint : 1;
204
205 /* The line contains a countpoint styl breakpoint */
206 guint is_countpoint : 1;
207
208 /* The line contains a watchpoint style breakpoint */
209 guint is_watchpoint : 1;
210
211 /* The line is an addition to the buffer */
212 guint is_add : 1;
213
214 /* The line has changed in the buffer */
215 guint is_change : 1;
216
217 /* The line is part of a deleted range in the buffer */
218 guint is_delete : 1;
219
220 /* The previous line was a delete */
221 guint is_prev_delete : 1;
222
223 /* The next line is a delete */
224 guint is_next_delete : 1;
225
226 /* The line contains a diagnostic error */
227 guint is_error : 1;
228
229 /* The line contains a diagnostic warning */
230 guint is_warning : 1;
231
232 /* The line contains a diagnostic note */
233 guint is_note : 1;
234 } LineInfo;
235
236 static void gbp_omni_gutter_renderer_reload_icons (GbpOmniGutterRenderer *self);
237 static void gutter_iface_init (IdeGutterInterface *iface);
238
239 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpOmniGutterRenderer,
240 gbp_omni_gutter_renderer,
241 GTK_SOURCE_TYPE_GUTTER_RENDERER,
242 G_IMPLEMENT_INTERFACE (IDE_TYPE_GUTTER, gutter_iface_init))
243
244 static GParamSpec *properties [N_PROPS];
245
246 static gint
int_to_string(guint value,const gchar ** outstr)247 int_to_string (guint value,
248 const gchar **outstr)
249 {
250 static struct{
251 guint value;
252 guint len;
253 gchar str[12];
254 } fi;
255
256 *outstr = fi.str;
257
258 if (value == fi.value)
259 return fi.len;
260
261 if G_LIKELY (value == fi.value + 1)
262 {
263 guint carry = 1;
264
265 for (gint i = fi.len - 1; i >= 0; i--)
266 {
267 fi.str[i] += carry;
268 carry = fi.str[i] == ':';
269
270 if (carry)
271 fi.str[i] = '0';
272 else
273 break;
274 }
275
276 if G_UNLIKELY (carry)
277 {
278 for (guint i = fi.len; i > 0; i--)
279 fi.str[i] = fi.str[i-1];
280
281 fi.len++;
282 fi.str[0] = '1';
283 fi.str[fi.len] = 0;
284 }
285
286 fi.value++;
287
288 return fi.len;
289 }
290
291 fi.len = snprintf (fi.str, sizeof fi.str - 1, "%u", value);
292 fi.str[fi.len] = 0;
293 fi.value = value;
294
295 return fi.len;
296 }
297
298 /*
299 * style_get_is_bold:
300 *
301 * This helper is used to extract the "bold" field from a GtkSourceStyle
302 * within a GtkSourceStyleScheme.
303 *
304 * Returns; %TRUE if @val was set to a trusted value.
305 */
306 static gboolean
style_get_is_bold(GtkSourceStyleScheme * scheme,const gchar * style_name,gboolean * val)307 style_get_is_bold (GtkSourceStyleScheme *scheme,
308 const gchar *style_name,
309 gboolean *val)
310 {
311 GtkSourceStyle *style;
312
313 g_assert (!scheme || GTK_SOURCE_IS_STYLE_SCHEME (scheme));
314 g_assert (style_name != NULL);
315 g_assert (val != NULL);
316
317 *val = FALSE;
318
319 if (scheme == NULL)
320 return FALSE;
321
322 if (NULL != (style = gtk_source_style_scheme_get_style (scheme, style_name)))
323 {
324 gboolean bold_set = FALSE;
325 g_object_get (style,
326 "bold-set", &bold_set,
327 "bold", val,
328 NULL);
329 return bold_set;
330 }
331
332 return FALSE;
333 }
334
335 /*
336 * get_style_rgba:
337 *
338 * Gets a #GdkRGBA for a particular field of a style within @scheme.
339 *
340 * @type should be set to BACKGROUND or FOREGROUND.
341 *
342 * If we fail to locate the style, @rgba is set to transparent black.
343 * such as #rgba(0,0,0,0).
344 *
345 * Returns: %TRUE if the value placed into @rgba can be trusted.
346 */
347 static gboolean
get_style_rgba(GtkSourceStyleScheme * scheme,const gchar * style_name,int type,GdkRGBA * rgba)348 get_style_rgba (GtkSourceStyleScheme *scheme,
349 const gchar *style_name,
350 int type,
351 GdkRGBA *rgba)
352 {
353 GtkSourceStyle *style;
354
355 g_assert (!scheme || GTK_SOURCE_IS_STYLE_SCHEME (scheme));
356 g_assert (style_name != NULL);
357 g_assert (type == FOREGROUND || type == BACKGROUND);
358 g_assert (rgba != NULL);
359
360 memset (rgba, 0, sizeof *rgba);
361
362 if (scheme == NULL)
363 return FALSE;
364
365 if (NULL != (style = gtk_source_style_scheme_get_style (scheme, style_name)))
366 {
367 g_autofree gchar *str = NULL;
368 gboolean set = FALSE;
369
370 g_object_get (style,
371 type ? "background" : "foreground", &str,
372 type ? "background-set" : "foreground-set", &set,
373 NULL);
374
375 if (str != NULL)
376 gdk_rgba_parse (rgba, str);
377
378 return set;
379 }
380
381 return FALSE;
382 }
383
384 static void
reload_style_colors(GbpOmniGutterRenderer * self,GtkSourceStyleScheme * scheme)385 reload_style_colors (GbpOmniGutterRenderer *self,
386 GtkSourceStyleScheme *scheme)
387 {
388 GtkStyleContext *context;
389 GtkTextView *view;
390 GtkStateFlags state;
391 GdkRGBA fg;
392 GdkRGBA bg;
393
394 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
395 g_assert (!scheme || GTK_SOURCE_IS_STYLE_SCHEME (scheme));
396
397 view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (self));
398 if (view == NULL)
399 return;
400
401 context = gtk_widget_get_style_context (GTK_WIDGET (view));
402 state = gtk_style_context_get_state (context);
403 gtk_style_context_get_color (context, state, &fg);
404 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
405 gtk_style_context_get_background_color (context, state, &bg);
406 G_GNUC_END_IGNORE_DEPRECATIONS;
407
408 /* Extract common values from style schemes. */
409 if (!get_style_rgba (scheme, "line-numbers", FOREGROUND, &self->text.fg))
410 self->text.fg = fg;
411
412 if (!get_style_rgba (scheme, "line-numbers", BACKGROUND, &self->text.bg))
413 self->text.bg = bg;
414
415 if (!style_get_is_bold (scheme, "line-numbers", &self->text.bold))
416 self->text.bold = FALSE;
417
418 if (!get_style_rgba (scheme, "current-line-number", FOREGROUND, &self->current.fg))
419 self->current.fg = fg;
420
421 if (!get_style_rgba (scheme, "current-line-number", BACKGROUND, &self->current.bg))
422 self->current.bg = bg;
423
424 if (!style_get_is_bold (scheme, "current-line-number", &self->current.bold))
425 self->current.bold = TRUE;
426
427 /* These gutter:: prefix values come from Builder's style-scheme xml
428 * files, but other style schemes may also support them now too.
429 */
430 if (!get_style_rgba (scheme, "gutter::added-line", FOREGROUND, &self->changes.add))
431 gdk_rgba_parse (&self->changes.add, "#8ae234");
432
433 if (!get_style_rgba (scheme, "gutter::changed-line", FOREGROUND, &self->changes.change))
434 gdk_rgba_parse (&self->changes.change, "#fcaf3e");
435
436 if (!get_style_rgba (scheme, "gutter::removed-line", FOREGROUND, &self->changes.remove))
437 gdk_rgba_parse (&self->changes.remove, "#ef2929");
438
439 /*
440 * These debugger:: prefix values come from Builder's style-scheme xml
441 * as well as in the IdeBuffer class. Other style schemes may also
442 * support them, though.
443 */
444 if (!get_style_rgba (scheme, "debugger::current-breakpoint", BACKGROUND, &self->stopped_bg))
445 gdk_rgba_parse (&self->stopped_bg, "#fcaf3e");
446
447 if (!get_style_rgba (scheme, "debugger::breakpoint", FOREGROUND, &self->bkpt.fg))
448 get_style_rgba (scheme, "selection", FOREGROUND, &self->bkpt.fg);
449 if (!get_style_rgba (scheme, "debugger::breakpoint", BACKGROUND, &self->bkpt.bg))
450 get_style_rgba (scheme, "selection", BACKGROUND, &self->bkpt.bg);
451 if (!style_get_is_bold (scheme, "debugger::breakpoint", &self->bkpt.bold))
452 self->bkpt.bold = FALSE;
453
454 /* Slight different color for countpoint, fallback to mix(selection,diff:add) */
455 if (!get_style_rgba (scheme, "debugger::countpoint", FOREGROUND, &self->ctpt.fg))
456 get_style_rgba (scheme, "selection", FOREGROUND, &self->ctpt.fg);
457 if (!get_style_rgba (scheme, "debugger::countpoint", BACKGROUND, &self->ctpt.bg))
458 {
459 get_style_rgba (scheme, "selection", BACKGROUND, &self->ctpt.bg);
460 self->ctpt.bg.red = (self->ctpt.bg.red + self->changes.add.red) / 2.0;
461 self->ctpt.bg.green = (self->ctpt.bg.green + self->changes.add.green) / 2.0;
462 self->ctpt.bg.blue = (self->ctpt.bg.blue + self->changes.add.blue) / 2.0;
463 }
464 if (!style_get_is_bold (scheme, "debugger::countpoint", &self->ctpt.bold))
465 self->ctpt.bold = FALSE;
466 }
467
468 static void
collect_breakpoint_info(IdeDebuggerBreakpoint * breakpoint,gpointer user_data)469 collect_breakpoint_info (IdeDebuggerBreakpoint *breakpoint,
470 gpointer user_data)
471 {
472 struct {
473 GArray *lines;
474 guint begin;
475 guint end;
476 } *bkpt_info = user_data;
477 guint line;
478
479 g_assert (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
480 g_assert (bkpt_info != NULL);
481
482 /* Debugger breakpoints are 1-based line numbers */
483 if (!(line = ide_debugger_breakpoint_get_line (breakpoint)))
484 return;
485
486 line--;
487
488 if (line >= bkpt_info->begin && line <= bkpt_info->end)
489 {
490 IdeDebuggerBreakMode mode = ide_debugger_breakpoint_get_mode (breakpoint);
491 LineInfo *info = &g_array_index (bkpt_info->lines, LineInfo, line - bkpt_info->begin);
492
493 info->is_watchpoint = !!(mode & IDE_DEBUGGER_BREAK_WATCHPOINT);
494 info->is_countpoint = !!(mode & IDE_DEBUGGER_BREAK_COUNTPOINT);
495 info->is_breakpoint = !!(mode & IDE_DEBUGGER_BREAK_BREAKPOINT);
496 }
497 }
498
499 static void
gbp_omni_gutter_renderer_load_breakpoints(GbpOmniGutterRenderer * self,GtkTextIter * begin,GtkTextIter * end,GArray * lines)500 gbp_omni_gutter_renderer_load_breakpoints (GbpOmniGutterRenderer *self,
501 GtkTextIter *begin,
502 GtkTextIter *end,
503 GArray *lines)
504 {
505 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
506 g_assert (begin != NULL);
507 g_assert (lines != NULL);
508 g_assert (lines->len > 0);
509
510 if (self->breakpoints != NULL)
511 {
512 struct {
513 GArray *lines;
514 guint begin;
515 guint end;
516 } info;
517
518 info.lines = lines;
519 info.begin = gtk_text_iter_get_line (begin);
520 info.end = gtk_text_iter_get_line (end);
521
522 ide_debugger_breakpoints_foreach (self->breakpoints,
523 (GFunc)collect_breakpoint_info,
524 &info);
525 }
526 }
527
528 static void
populate_diagnostics_cb(guint line,IdeDiagnosticSeverity severity,gpointer user_data)529 populate_diagnostics_cb (guint line,
530 IdeDiagnosticSeverity severity,
531 gpointer user_data)
532 {
533 LineInfo *info;
534 struct {
535 GArray *lines;
536 guint begin_line;
537 guint end_line;
538 } *state = user_data;
539
540 g_assert (line >= state->begin_line);
541 g_assert (line <= state->end_line);
542
543 info = &g_array_index (state->lines, LineInfo, line - state->begin_line);
544 info->is_warning |= severity == IDE_DIAGNOSTIC_WARNING
545 || severity == IDE_DIAGNOSTIC_DEPRECATED
546 || severity == IDE_DIAGNOSTIC_UNUSED;
547 info->is_error |= severity == IDE_DIAGNOSTIC_ERROR || severity == IDE_DIAGNOSTIC_FATAL;
548 info->is_note |= severity == IDE_DIAGNOSTIC_NOTE;
549 }
550
551 static void
populate_changes_cb(guint line,IdeBufferLineChange change,gpointer user_data)552 populate_changes_cb (guint line,
553 IdeBufferLineChange change,
554 gpointer user_data)
555 {
556 LineInfo *info;
557 struct {
558 GArray *lines;
559 guint begin_line;
560 guint end_line;
561 } *state = user_data;
562 guint pos;
563
564 g_assert (line >= state->begin_line);
565 g_assert (line <= state->end_line);
566
567 pos = line - state->begin_line;
568
569 info = &g_array_index (state->lines, LineInfo, pos);
570 info->is_add = !!(change & IDE_BUFFER_LINE_CHANGE_ADDED);
571 info->is_change = !!(change & IDE_BUFFER_LINE_CHANGE_CHANGED);
572 info->is_delete = !!(change & IDE_BUFFER_LINE_CHANGE_DELETED);
573 info->is_prev_delete = !!(change & IDE_BUFFER_LINE_CHANGE_PREVIOUS_DELETED);
574
575 if (pos > 0)
576 {
577 LineInfo *last = &g_array_index (state->lines, LineInfo, pos - 1);
578
579 info->is_prev_delete |= last->is_delete;
580 last->is_next_delete = info->is_delete;
581 }
582 }
583
584 static void
gbp_omni_gutter_renderer_load_basic(GbpOmniGutterRenderer * self,GtkTextIter * begin,GArray * lines)585 gbp_omni_gutter_renderer_load_basic (GbpOmniGutterRenderer *self,
586 GtkTextIter *begin,
587 GArray *lines)
588 {
589 IdeBufferChangeMonitor *monitor;
590 IdeDiagnostics *diagnostics;
591 GtkTextBuffer *buffer;
592 GFile *file;
593 struct {
594 GArray *lines;
595 guint begin_line;
596 guint end_line;
597 } state;
598
599 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
600 g_assert (begin != NULL);
601 g_assert (lines != NULL);
602 g_assert (lines->len > 0);
603
604 buffer = gtk_text_iter_get_buffer (begin);
605 if (!IDE_IS_BUFFER (buffer))
606 return;
607
608 file = ide_buffer_get_file (IDE_BUFFER (buffer));
609
610 state.lines = lines;
611 state.begin_line = gtk_text_iter_get_line (begin);
612 state.end_line = state.begin_line + lines->len;
613
614 if ((diagnostics = ide_buffer_get_diagnostics (IDE_BUFFER (buffer))))
615 ide_diagnostics_foreach_line_in_range (diagnostics,
616 file,
617 state.begin_line,
618 state.end_line,
619 populate_diagnostics_cb,
620 &state);
621
622 if ((monitor = ide_buffer_get_change_monitor (IDE_BUFFER (buffer))))
623 ide_buffer_change_monitor_foreach_change (monitor,
624 state.begin_line,
625 state.end_line,
626 populate_changes_cb,
627 &state);
628 }
629
630 static inline gint
count_num_digits(gint num_lines)631 count_num_digits (gint num_lines)
632 {
633 if (num_lines < 100)
634 return 2;
635 else if (num_lines < 1000)
636 return 3;
637 else if (num_lines < 10000)
638 return 4;
639 else if (num_lines < 100000)
640 return 5;
641 else if (num_lines < 1000000)
642 return 6;
643 else
644 return 10;
645 }
646
647 static gint
calculate_diagnostics_size(gint height)648 calculate_diagnostics_size (gint height)
649 {
650 static guint sizes[] = { 64, 48, 32, 24, 16, 8 };
651
652 for (guint i = 0; i < G_N_ELEMENTS (sizes); i++)
653 {
654 if (height >= sizes[i])
655 return sizes[i];
656 }
657
658 return sizes [G_N_ELEMENTS (sizes) - 1];
659 }
660
661 static void
gbp_omni_gutter_renderer_recalculate_size(GbpOmniGutterRenderer * self)662 gbp_omni_gutter_renderer_recalculate_size (GbpOmniGutterRenderer *self)
663 {
664 g_autofree gchar *numbers = NULL;
665 GtkTextBuffer *buffer;
666 GtkTextView *view;
667 PangoLayout *layout;
668 GtkTextIter end;
669 guint line;
670 int height;
671 gint size = 0;
672
673 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
674
675 /* There is nothing we can do until a view has been attached. */
676 view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (self));
677 if (!IDE_IS_SOURCE_VIEW (view))
678 return;
679
680 /*
681 * First, we need to get the size of the text for the last line of the
682 * buffer (which will be the longest). We size the font with '9' since it
683 * will generally be one of the widest of the numbers. Although, we only
684 * "support" * monospace anyway, so it shouldn't be drastic if we're off.
685 */
686
687 buffer = gtk_text_view_get_buffer (view);
688 gtk_text_buffer_get_end_iter (buffer, &end);
689 line = gtk_text_iter_get_line (&end) + 1;
690
691 self->n_chars = count_num_digits (line);
692 numbers = g_strnfill (self->n_chars, '9');
693
694 /*
695 * Stash the font description for future use.
696 */
697 g_clear_pointer (&self->scaled_font_desc, pango_font_description_free);
698 self->scaled_font_desc = ide_source_view_get_scaled_font_desc (IDE_SOURCE_VIEW (view));
699
700 /*
701 * Get the font description used by the IdeSourceView so we can
702 * match the font styling as much as possible.
703 */
704 layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), numbers);
705 pango_layout_set_font_description (layout, self->scaled_font_desc);
706
707 /*
708 * Now cache the width of the text layout so we can simplify our
709 * positioning later. We simply size everything the same and then
710 * align to the right to reduce the draw overhead.
711 */
712 pango_layout_get_pixel_size (layout, &self->number_width, &height);
713
714 /*
715 * Calculate the nearest size for diagnostics so they scale somewhat
716 * reasonable with the character size.
717 */
718 self->diag_size = calculate_diagnostics_size (MAX (16, height));
719 g_assert (self->diag_size > 0);
720
721 /* Now calculate the size based on enabled features */
722 size = 2;
723 if (self->show_line_diagnostics)
724 size += self->diag_size + 2;
725 if (self->show_line_numbers)
726 size += self->number_width + 2;
727
728 /* The arrow overlaps the changes if we can have breakpoints,
729 * otherwise we just need the space for the line changes.
730 */
731 if (self->breakpoints != NULL)
732 size += ARROW_WIDTH + 2;
733 else if (self->show_line_changes)
734 size += CHANGE_WIDTH + 2;
735
736 /* Update the size and ensure we are re-drawn */
737 gtk_source_gutter_renderer_set_size (GTK_SOURCE_GUTTER_RENDERER (self), size);
738 gtk_source_gutter_renderer_queue_draw (GTK_SOURCE_GUTTER_RENDERER (self));
739
740 g_clear_object (&layout);
741 }
742
743 static void
gbp_omni_gutter_renderer_notify_font_desc(GbpOmniGutterRenderer * self,GParamSpec * pspec,IdeSourceView * view)744 gbp_omni_gutter_renderer_notify_font_desc (GbpOmniGutterRenderer *self,
745 GParamSpec *pspec,
746 IdeSourceView *view)
747 {
748 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
749 g_assert (IDE_IS_SOURCE_VIEW (view));
750
751 gbp_omni_gutter_renderer_recalculate_size (self);
752 gbp_omni_gutter_renderer_reload_icons (self);
753 }
754
755 static void
gbp_omni_gutter_renderer_end(GtkSourceGutterRenderer * renderer)756 gbp_omni_gutter_renderer_end (GtkSourceGutterRenderer *renderer)
757 {
758 GbpOmniGutterRenderer *self = (GbpOmniGutterRenderer *)renderer;
759
760 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
761
762 g_clear_object (&self->layout);
763 }
764
765 static void
gbp_omni_gutter_renderer_begin(GtkSourceGutterRenderer * renderer,cairo_t * cr,GdkRectangle * bg_area,GdkRectangle * cell_area,GtkTextIter * begin,GtkTextIter * end)766 gbp_omni_gutter_renderer_begin (GtkSourceGutterRenderer *renderer,
767 cairo_t *cr,
768 GdkRectangle *bg_area,
769 GdkRectangle *cell_area,
770 GtkTextIter *begin,
771 GtkTextIter *end)
772 {
773 GbpOmniGutterRenderer *self = (GbpOmniGutterRenderer *)renderer;
774 GtkTextTagTable *table;
775 GtkTextBuffer *buffer;
776 IdeSourceView *view;
777 GtkTextTag *tag;
778 GtkTextIter bkpt;
779 guint end_line;
780
781 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (renderer));
782 g_assert (cr != NULL);
783 g_assert (bg_area != NULL);
784 g_assert (cell_area != NULL);
785 g_assert (begin != NULL);
786 g_assert (end != NULL);
787
788 /* Draw the full background color up front */
789 gdk_cairo_rectangle (cr, cell_area);
790 gdk_cairo_set_source_rgba (cr, &self->text.bg);
791 cairo_fill (cr);
792
793 self->line_spacing = g_settings_get_int (self->settings, "line-spacing");
794
795 /*
796 * This is the start of our draw process. The first thing we want to
797 * do is collect as much information as we'll need when doing the
798 * actual draw. That helps us coalesce similar work together, which is
799 * good for the CPU usage. We are *very* sensitive to CPU usage here
800 * as the GtkTextView does not pixel cache the gutter.
801 */
802
803 self->stopped_line = -1;
804
805 /* Locate the current stopped breakpoint if any. */
806 buffer = gtk_text_iter_get_buffer (begin);
807 table = gtk_text_buffer_get_tag_table (buffer);
808 tag = gtk_text_tag_table_lookup (table, "debugger::current-breakpoint");
809 if (tag != NULL)
810 {
811 bkpt = *begin;
812 gtk_text_iter_backward_char (&bkpt);
813 if (gtk_text_iter_forward_to_tag_toggle (&bkpt, tag) &&
814 gtk_text_iter_starts_tag (&bkpt, tag))
815 self->stopped_line = gtk_text_iter_get_line (&bkpt);
816 }
817
818 /*
819 * This function is called before we render any of the lines in
820 * the gutter. To reduce our overhead, we want to collect information
821 * for all of the line numbers upfront.
822 */
823
824 view = IDE_SOURCE_VIEW (gtk_source_gutter_renderer_get_view (renderer));
825
826 self->begin_line = gtk_text_iter_get_line (begin);
827 end_line = gtk_text_iter_get_line (end);
828
829 ide_source_view_get_visual_position (view, &self->cursor_line, NULL);
830
831 /* Give ourselves a fresh array to stash our line info */
832 g_array_set_size (self->lines, end_line - self->begin_line + 1);
833 memset (self->lines->data, 0, self->lines->len * sizeof (LineInfo));
834
835 /* Now load breakpoints, diagnostics, and line changes */
836 gbp_omni_gutter_renderer_load_basic (self, begin, self->lines);
837 gbp_omni_gutter_renderer_load_breakpoints (self, begin, end, self->lines);
838
839 /* Create a new layout for rendering lines to */
840 self->layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), "");
841 pango_layout_set_alignment (self->layout, PANGO_ALIGN_RIGHT);
842 pango_layout_set_font_description (self->layout, self->scaled_font_desc);
843 pango_layout_set_width (self->layout, (cell_area->width - ARROW_WIDTH - 4) * PANGO_SCALE);
844 }
845
846 static gboolean
gbp_omni_gutter_renderer_query_activatable(GtkSourceGutterRenderer * renderer,GtkTextIter * begin,GdkRectangle * area,GdkEvent * event)847 gbp_omni_gutter_renderer_query_activatable (GtkSourceGutterRenderer *renderer,
848 GtkTextIter *begin,
849 GdkRectangle *area,
850 GdkEvent *event)
851 {
852 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (renderer));
853 g_assert (begin != NULL);
854 g_assert (area != NULL);
855 g_assert (event != NULL);
856
857 /* Clicking will move the cursor, so always TRUE */
858
859 return TRUE;
860 }
861
862 static void
animate_at_iter(GbpOmniGutterRenderer * self,GdkRectangle * area,GtkTextIter * iter)863 animate_at_iter (GbpOmniGutterRenderer *self,
864 GdkRectangle *area,
865 GtkTextIter *iter)
866 {
867 DzlBoxTheatric *theatric;
868 GtkTextView *view;
869
870 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
871 g_assert (area != NULL);
872 g_assert (iter != NULL);
873
874 /* Show a little bullet animation shooting right */
875
876 view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (self));
877
878 theatric = g_object_new (DZL_TYPE_BOX_THEATRIC,
879 "alpha", 0.3,
880 "background", "#729fcf",
881 "height", area->height,
882 "target", view,
883 "width", area->width,
884 "x", area->x,
885 "y", area->y,
886 NULL);
887
888 dzl_object_animate_full (theatric,
889 DZL_ANIMATION_EASE_IN_CUBIC,
890 100,
891 gtk_widget_get_frame_clock (GTK_WIDGET (view)),
892 g_object_unref,
893 theatric,
894 "x", area->x + 250,
895 "alpha", 0.0,
896 NULL);
897 }
898
899 static void
gbp_omni_gutter_renderer_activate(GtkSourceGutterRenderer * renderer,GtkTextIter * iter,GdkRectangle * area,GdkEvent * event)900 gbp_omni_gutter_renderer_activate (GtkSourceGutterRenderer *renderer,
901 GtkTextIter *iter,
902 GdkRectangle *area,
903 GdkEvent *event)
904 {
905 GbpOmniGutterRenderer *self = (GbpOmniGutterRenderer *)renderer;
906 IdeDebuggerBreakpoint *breakpoint;
907 IdeDebuggerBreakMode break_type = IDE_DEBUGGER_BREAK_NONE;
908 g_autofree gchar *path = NULL;
909 g_autoptr(IdeContext) context = NULL;
910 IdeDebugManager *debug_manager;
911 GtkTextBuffer *buffer;
912 GtkTextIter begin;
913 GtkTextIter end;
914 GFile *file;
915 guint line;
916
917 IDE_ENTRY;
918
919 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
920 g_assert (iter != NULL);
921 g_assert (area != NULL);
922 g_assert (event != NULL);
923
924 /* TODO: We could check for event->button.button to see if we
925 * can display a popover with information such as
926 * diagnostics, or breakpoints, or git blame.
927 */
928
929 buffer = gtk_text_iter_get_buffer (iter);
930
931 /* Select this row if it isn't currently selected */
932 if (!gtk_text_buffer_get_selection_bounds (buffer, &begin, &end) &&
933 gtk_text_iter_get_line (&begin) != gtk_text_iter_get_line (iter))
934 gtk_text_buffer_select_range (buffer, iter, iter);
935
936 /* Nothing more we can do if this file doesn't support breakpoints */
937 if (self->breakpoints == NULL)
938 return;
939
940 context = ide_buffer_ref_context (IDE_BUFFER (buffer));
941 debug_manager = ide_debug_manager_from_context (context);
942
943 line = gtk_text_iter_get_line (iter) + 1;
944 file = ide_debugger_breakpoints_get_file (self->breakpoints);
945 path = g_file_get_path (file);
946
947 /* TODO: Should we show a Popover here to select the type? */
948 IDE_TRACE_MSG ("Toggle breakpoint on line %u [breakpoints=%p]",
949 line, self->breakpoints);
950
951 breakpoint = ide_debugger_breakpoints_get_line (self->breakpoints, line);
952 if (breakpoint != NULL)
953 break_type = ide_debugger_breakpoint_get_mode (breakpoint);
954
955 switch (break_type)
956 {
957 case IDE_DEBUGGER_BREAK_NONE:
958 {
959 g_autoptr(IdeDebuggerBreakpoint) to_insert = NULL;
960
961 to_insert = ide_debugger_breakpoint_new (NULL);
962
963 ide_debugger_breakpoint_set_line (to_insert, line);
964 ide_debugger_breakpoint_set_file (to_insert, path);
965 ide_debugger_breakpoint_set_mode (to_insert, IDE_DEBUGGER_BREAK_BREAKPOINT);
966 ide_debugger_breakpoint_set_enabled (to_insert, TRUE);
967
968 _ide_debug_manager_add_breakpoint (debug_manager, to_insert);
969 }
970 break;
971
972 case IDE_DEBUGGER_BREAK_BREAKPOINT:
973 case IDE_DEBUGGER_BREAK_COUNTPOINT:
974 case IDE_DEBUGGER_BREAK_WATCHPOINT:
975 if (breakpoint != NULL)
976 {
977 _ide_debug_manager_remove_breakpoint (debug_manager, breakpoint);
978 animate_at_iter (self, area, iter);
979 }
980 break;
981
982 default:
983 g_return_if_reached ();
984 }
985
986 /*
987 * We will wait for changes to be applied to the #IdeDebuggerBreakpoints
988 * by the #IdeDebugManager. That will cause the gutter to be invalidated
989 * and redrawn.
990 */
991
992 IDE_EXIT;
993 }
994
995 static void
draw_breakpoint_bg(GbpOmniGutterRenderer * self,cairo_t * cr,GdkRectangle * bg_area,LineInfo * info,GtkSourceGutterRendererState state)996 draw_breakpoint_bg (GbpOmniGutterRenderer *self,
997 cairo_t *cr,
998 GdkRectangle *bg_area,
999 LineInfo *info,
1000 GtkSourceGutterRendererState state)
1001 {
1002 GdkRectangle area;
1003 GdkRGBA rgba;
1004
1005 g_assert (GTK_SOURCE_IS_GUTTER_RENDERER (self));
1006 g_assert (cr != NULL);
1007 g_assert (bg_area != NULL);
1008
1009 /*
1010 * This draws a little arrow starting from the left and pointing
1011 * over the line changes portion of the gutter.
1012 */
1013
1014 area.x = bg_area->x;
1015 area.y = bg_area->y;
1016 area.height = bg_area->height;
1017 area.width = bg_area->width;
1018
1019 cairo_move_to (cr, area.x, area.y);
1020 cairo_line_to (cr,
1021 dzl_cairo_rectangle_x2 (&area) - ARROW_WIDTH,
1022 area.y);
1023 cairo_line_to (cr,
1024 dzl_cairo_rectangle_x2 (&area),
1025 dzl_cairo_rectangle_middle (&area));
1026 cairo_line_to (cr,
1027 dzl_cairo_rectangle_x2 (&area) - ARROW_WIDTH,
1028 dzl_cairo_rectangle_y2 (&area));
1029 cairo_line_to (cr, area.x, dzl_cairo_rectangle_y2 (&area));
1030 cairo_close_path (cr);
1031
1032 if (info->is_countpoint)
1033 rgba = self->ctpt.bg;
1034 else
1035 rgba = self->bkpt.bg;
1036
1037 /*
1038 * Tweak the brightness based on if we are in pre-light and
1039 * if we are also still an active breakpoint.
1040 */
1041
1042 if ((state & GTK_SOURCE_GUTTER_RENDERER_STATE_PRELIT) != 0)
1043 {
1044 if (IS_BREAKPOINT (info))
1045 rgba.alpha *= 0.8;
1046 else
1047 rgba.alpha *= 0.4;
1048 }
1049
1050 /* And draw... */
1051
1052 gdk_cairo_set_source_rgba (cr, &rgba);
1053 cairo_fill (cr);
1054 }
1055
1056 static void
draw_line_change(GbpOmniGutterRenderer * self,cairo_t * cr,GdkRectangle * area,LineInfo * info,guint line,GtkSourceGutterRendererState state)1057 draw_line_change (GbpOmniGutterRenderer *self,
1058 cairo_t *cr,
1059 GdkRectangle *area,
1060 LineInfo *info,
1061 guint line,
1062 GtkSourceGutterRendererState state)
1063 {
1064 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1065 g_assert (cr != NULL);
1066 g_assert (area != NULL);
1067
1068 /*
1069 * Draw a simple line with the appropriate color from the style scheme
1070 * based on the type of change we have.
1071 */
1072
1073 if (info->is_add || info->is_change)
1074 {
1075 cairo_rectangle (cr,
1076 area->x + area->width - 2 - CHANGE_WIDTH,
1077 area->y,
1078 CHANGE_WIDTH,
1079 area->y + area->height);
1080
1081 if (info->is_add)
1082 gdk_cairo_set_source_rgba (cr, &self->changes.add);
1083 else
1084 gdk_cairo_set_source_rgba (cr, &self->changes.change);
1085
1086 cairo_fill (cr);
1087 }
1088
1089 if (line == 0 && info->is_prev_delete)
1090 {
1091 cairo_move_to (cr,
1092 area->x + area->width,
1093 area->y);
1094 cairo_line_to (cr,
1095 area->x + area->width - DELETE_WIDTH,
1096 area->y + DELETE_HEIGHT / 2);
1097 cairo_line_to (cr,
1098 area->x + area->width - DELETE_WIDTH,
1099 area->y);
1100 cairo_line_to (cr,
1101 area->x + area->width,
1102 area->y);
1103 gdk_cairo_set_source_rgba (cr, &self->changes.remove);
1104 cairo_fill (cr);
1105 }
1106
1107 if (info->is_next_delete && !info->is_delete)
1108 {
1109 cairo_move_to (cr,
1110 area->x + area->width,
1111 area->y + area->height);
1112 cairo_line_to (cr,
1113 area->x + area->width - DELETE_WIDTH,
1114 area->y + area->height);
1115 cairo_line_to (cr,
1116 area->x + area->width - DELETE_WIDTH,
1117 area->y + area->height - (DELETE_HEIGHT / 2));
1118 cairo_line_to (cr,
1119 area->x + area->width,
1120 area->y + area->height);
1121 gdk_cairo_set_source_rgba (cr, &self->changes.remove);
1122 cairo_fill (cr);
1123 }
1124
1125 if (info->is_delete && !info->is_prev_delete)
1126 {
1127 cairo_move_to (cr,
1128 area->x + area->width,
1129 area->y);
1130 cairo_line_to (cr,
1131 area->x + area->width - DELETE_WIDTH,
1132 area->y);
1133 cairo_line_to (cr,
1134 area->x + area->width - DELETE_WIDTH,
1135 area->y + (DELETE_HEIGHT / 2));
1136 cairo_line_to (cr,
1137 area->x + area->width,
1138 area->y);
1139 gdk_cairo_set_source_rgba (cr, &self->changes.remove);
1140 cairo_fill (cr);
1141 }
1142 }
1143
1144 static void
draw_diagnostic(GbpOmniGutterRenderer * self,cairo_t * cr,GdkRectangle * area,LineInfo * info,gint diag_size,GtkSourceGutterRendererState state)1145 draw_diagnostic (GbpOmniGutterRenderer *self,
1146 cairo_t *cr,
1147 GdkRectangle *area,
1148 LineInfo *info,
1149 gint diag_size,
1150 GtkSourceGutterRendererState state)
1151 {
1152 cairo_surface_t *surface = NULL;
1153
1154 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1155 g_assert (cr != NULL);
1156 g_assert (area != NULL);
1157 g_assert (diag_size > 0);
1158
1159 if (IS_BREAKPOINT (info) || (state & GTK_SOURCE_GUTTER_RENDERER_STATE_PRELIT))
1160 {
1161 if (info->is_error)
1162 surface = self->error_selected_surface;
1163 else if (info->is_warning)
1164 surface = self->warning_selected_surface;
1165 else if (info->is_note)
1166 surface = self->note_selected_surface;
1167 }
1168 else
1169 {
1170 if (info->is_error)
1171 surface = self->error_surface;
1172 else if (info->is_warning)
1173 surface = self->warning_surface;
1174 else if (info->is_note)
1175 surface = self->note_surface;
1176 }
1177
1178 if (surface != NULL)
1179 {
1180 cairo_rectangle (cr,
1181 area->x + 2,
1182 area->y + ((area->height - diag_size) / 2),
1183 diag_size, diag_size);
1184 cairo_set_source_surface (cr,
1185 surface,
1186 area->x + 2,
1187 area->y + ((area->height - diag_size) / 2));
1188 cairo_paint (cr);
1189 }
1190 }
1191
1192 static void
gbp_omni_gutter_renderer_draw(GtkSourceGutterRenderer * renderer,cairo_t * cr,GdkRectangle * bg_area,GdkRectangle * cell_area,GtkTextIter * begin,GtkTextIter * end,GtkSourceGutterRendererState state)1193 gbp_omni_gutter_renderer_draw (GtkSourceGutterRenderer *renderer,
1194 cairo_t *cr,
1195 GdkRectangle *bg_area,
1196 GdkRectangle *cell_area,
1197 GtkTextIter *begin,
1198 GtkTextIter *end,
1199 GtkSourceGutterRendererState state)
1200 {
1201 GbpOmniGutterRenderer *self = (GbpOmniGutterRenderer *)renderer;
1202 GtkTextView *view;
1203 gboolean has_focus;
1204 gboolean highlight_line;
1205 guint line;
1206
1207 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1208 g_assert (cr != NULL);
1209 g_assert (bg_area != NULL);
1210 g_assert (cell_area != NULL);
1211 g_assert (begin != NULL);
1212 g_assert (end != NULL);
1213
1214 /*
1215 * This is our primary draw routine. It is called for every line that
1216 * is visible. We are incredibly sensitive to performance churn here
1217 * so it is important that we be as minimal as possible while
1218 * retaining the features we need.
1219 */
1220
1221 view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (self));
1222 highlight_line = gtk_source_view_get_highlight_current_line (GTK_SOURCE_VIEW (view));
1223 has_focus = gtk_widget_has_focus (GTK_WIDGET (view));
1224
1225 line = gtk_text_iter_get_line (begin);
1226
1227 if ((line - self->begin_line) < self->lines->len)
1228 {
1229 LineInfo *info = &g_array_index (self->lines, LineInfo, line - self->begin_line);
1230 gboolean active = state & GTK_SOURCE_GUTTER_RENDERER_STATE_PRELIT;
1231 gboolean has_breakpoint = FALSE;
1232 gboolean bold = FALSE;
1233
1234 /*
1235 * Draw some background for the line so that it looks like the
1236 * breakpoint arrow draws over it. Debugger break line takes
1237 * precidence over the current highlight line. Also, ensure that
1238 * the view is drawing the highlight line first.
1239 */
1240 if (line == self->stopped_line)
1241 {
1242 gdk_cairo_rectangle (cr, bg_area);
1243 gdk_cairo_set_source_rgba (cr, &self->stopped_bg);
1244 cairo_fill (cr);
1245 }
1246 else if (highlight_line && has_focus && (state & GTK_SOURCE_GUTTER_RENDERER_STATE_CURSOR))
1247 {
1248 gdk_cairo_rectangle (cr, bg_area);
1249 gdk_cairo_set_source_rgba (cr, &self->current.bg);
1250 cairo_fill (cr);
1251 }
1252
1253 /* Draw line changes next so it will show up underneath the
1254 * breakpoint arrows.
1255 */
1256 if (self->show_line_changes && IS_LINE_CHANGE (info))
1257 draw_line_change (self, cr, cell_area, info, line, state);
1258
1259 /* Draw breakpoint arrows if we have any breakpoints that could
1260 * potentially match.
1261 */
1262 if (self->breakpoints != NULL)
1263 {
1264 has_breakpoint = IS_BREAKPOINT (info);
1265 if (has_breakpoint || active)
1266 draw_breakpoint_bg (self, cr, cell_area, info, state);
1267 }
1268
1269 /* Now that we might have an altered background for the line,
1270 * we can draw the diagnostic icon (with possibly altered
1271 * color for symbolic icon).
1272 */
1273 if (self->show_line_diagnostics && IS_DIAGNOSTIC (info))
1274 draw_diagnostic (self, cr, cell_area, info, self->diag_size, state);
1275
1276 /*
1277 * Now draw the line numbers if we are showing them. Ensure
1278 * we tweak the style to match closely to how the default
1279 * gtksourceview lines gutter renderer does it.
1280 */
1281 if (self->show_line_numbers)
1282 {
1283 const gchar *linestr = NULL;
1284 gint len;
1285 guint shown_line;
1286
1287 if (!self->show_relative_line_numbers || line == self->cursor_line)
1288 shown_line = line + 1;
1289 else if (line > self->cursor_line)
1290 shown_line = line - self->cursor_line;
1291 else
1292 shown_line = self->cursor_line - line;
1293
1294 len = int_to_string (shown_line, &linestr);
1295 pango_layout_set_text (self->layout, linestr, len);
1296
1297 cairo_move_to (cr, cell_area->x, cell_area->y);
1298
1299 if (has_breakpoint || (self->breakpoints != NULL && active))
1300 {
1301 gdk_cairo_set_source_rgba (cr, &self->bkpt.fg);
1302 bold = self->bkpt.bold;
1303 }
1304 else if (state & GTK_SOURCE_GUTTER_RENDERER_STATE_CURSOR)
1305 {
1306 gdk_cairo_set_source_rgba (cr, &self->current.fg);
1307 bold = self->current.bold;
1308 }
1309 else
1310 {
1311 gdk_cairo_set_source_rgba (cr, &self->text.fg);
1312 bold = self->text.bold;
1313 }
1314
1315 /* Current line is always bold */
1316 if (state & GTK_SOURCE_GUTTER_RENDERER_STATE_CURSOR)
1317 bold |= self->current.bold;
1318
1319 cairo_move_to (cr, cell_area->x, cell_area->y + self->line_spacing);
1320 pango_layout_set_attributes (self->layout, bold ? self->bold_attrs : NULL);
1321 pango_cairo_show_layout (cr, self->layout);
1322 }
1323 }
1324 }
1325
1326 static cairo_surface_t *
get_icon_surface(GbpOmniGutterRenderer * self,GtkWidget * widget,const gchar * icon_name,gint size,gboolean selected)1327 get_icon_surface (GbpOmniGutterRenderer *self,
1328 GtkWidget *widget,
1329 const gchar *icon_name,
1330 gint size,
1331 gboolean selected)
1332 {
1333 g_autoptr(GtkIconInfo) info = NULL;
1334 GtkIconTheme *icon_theme;
1335 GdkScreen *screen;
1336 GtkIconLookupFlags flags;
1337 gint scale;
1338
1339 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1340 g_assert (GTK_IS_WIDGET (widget));
1341 g_assert (icon_name != NULL);
1342 g_assert (size > 0);
1343
1344 /*
1345 * This deals with loading a given icon by icon name and trying to
1346 * apply our current style as the symbolic colors. We do not support
1347 * error/warning/etc for symbolic icons so they are all replaced with
1348 * the proper foreground color.
1349 *
1350 * If selected is set, we alter the color to make sure it will look
1351 * good on top of a breakpoint arrow.
1352 */
1353
1354 screen = gtk_widget_get_screen (widget);
1355 icon_theme = gtk_icon_theme_get_for_screen (screen);
1356
1357 flags = GTK_ICON_LOOKUP_USE_BUILTIN;
1358 scale = gtk_widget_get_scale_factor (widget);
1359
1360 info = gtk_icon_theme_lookup_icon_for_scale (icon_theme, icon_name, size, scale, flags);
1361
1362 if (info != NULL)
1363 {
1364 g_autoptr(GdkPixbuf) pixbuf = NULL;
1365
1366 if (gtk_icon_info_is_symbolic (info))
1367 {
1368 GdkRGBA fg;
1369
1370 if (selected)
1371 fg = self->bkpt.fg;
1372 else
1373 fg = self->text.fg;
1374
1375 pixbuf = gtk_icon_info_load_symbolic (info, &fg, &fg, &fg, &fg, NULL, NULL);
1376 }
1377 else
1378 pixbuf = gtk_icon_info_load_icon (info, NULL);
1379
1380 if (pixbuf != NULL)
1381 return gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, NULL);
1382 }
1383
1384 return NULL;
1385 }
1386
1387 static void
gbp_omni_gutter_renderer_reload_icons(GbpOmniGutterRenderer * self)1388 gbp_omni_gutter_renderer_reload_icons (GbpOmniGutterRenderer *self)
1389 {
1390 GtkTextView *view;
1391
1392 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1393
1394 /*
1395 * This isn't ideal (we should find a better way to cache icons that
1396 * is safe with scale and foreground color changes we need).
1397 *
1398 * TODO: Create something similar to pixbuf helpers that allow for
1399 * more control over the cache key so it can be shared between
1400 * multiple instances.
1401 */
1402
1403 g_clear_pointer (&self->note_surface, cairo_surface_destroy);
1404 g_clear_pointer (&self->warning_surface, cairo_surface_destroy);
1405 g_clear_pointer (&self->error_surface, cairo_surface_destroy);
1406 g_clear_pointer (&self->note_selected_surface, cairo_surface_destroy);
1407 g_clear_pointer (&self->warning_selected_surface, cairo_surface_destroy);
1408 g_clear_pointer (&self->error_selected_surface, cairo_surface_destroy);
1409
1410 view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (self));
1411 if (view == NULL)
1412 return;
1413
1414 self->note_surface = get_icon_surface (self, GTK_WIDGET (view), "dialog-information-symbolic", self->diag_size, FALSE);
1415 self->warning_surface = get_icon_surface (self, GTK_WIDGET (view), "dialog-warning-symbolic", self->diag_size, FALSE);
1416 self->error_surface = get_icon_surface (self, GTK_WIDGET (view), "builder-build-stop-symbolic", self->diag_size, FALSE);
1417
1418 self->note_selected_surface = get_icon_surface (self, GTK_WIDGET (view), "dialog-information-symbolic", self->diag_size, TRUE);
1419 self->warning_selected_surface = get_icon_surface (self, GTK_WIDGET (view), "dialog-warning-symbolic", self->diag_size, TRUE);
1420 self->error_selected_surface = get_icon_surface (self, GTK_WIDGET (view), "builder-build-stop-symbolic", self->diag_size, TRUE);
1421 }
1422
1423 static void
gbp_omni_gutter_renderer_reload(GbpOmniGutterRenderer * self)1424 gbp_omni_gutter_renderer_reload (GbpOmniGutterRenderer *self)
1425 {
1426 g_autoptr(IdeDebuggerBreakpoints) breakpoints = NULL;
1427 GtkTextBuffer *buffer;
1428 GtkTextView *view;
1429
1430 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1431
1432 view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (self));
1433 buffer = gtk_text_view_get_buffer (view);
1434
1435 if (IDE_IS_BUFFER (buffer))
1436 {
1437 g_autoptr(IdeContext) context = NULL;
1438 IdeDebugManager *debug_manager;
1439 const gchar *lang_id;
1440
1441 context = ide_buffer_ref_context (IDE_BUFFER (buffer));
1442 debug_manager = ide_debug_manager_from_context (context);
1443 lang_id = ide_buffer_get_language_id (IDE_BUFFER (buffer));
1444
1445 if (ide_debug_manager_supports_language (debug_manager, lang_id))
1446 {
1447 GFile *file = ide_buffer_get_file (IDE_BUFFER (buffer));
1448
1449 breakpoints = ide_debug_manager_get_breakpoints_for_file (debug_manager, file);
1450 }
1451 }
1452
1453 /* Replace our previous breakpoints */
1454 g_set_object (&self->breakpoints, breakpoints);
1455
1456 /* Reload icons and then recalcuate our physical size */
1457 gbp_omni_gutter_renderer_recalculate_size (self);
1458 gbp_omni_gutter_renderer_reload_icons (self);
1459 }
1460
1461 static void
gbp_omni_gutter_renderer_notify_buffer(GbpOmniGutterRenderer * self,GParamSpec * pspec,IdeSourceView * view)1462 gbp_omni_gutter_renderer_notify_buffer (GbpOmniGutterRenderer *self,
1463 GParamSpec *pspec,
1464 IdeSourceView *view)
1465 {
1466 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1467 g_assert (IDE_IS_SOURCE_VIEW (view));
1468
1469 if (self->buffer_signals != NULL)
1470 {
1471 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1472
1473 if (!IDE_IS_BUFFER (buffer))
1474 buffer = NULL;
1475
1476 dzl_signal_group_set_target (self->buffer_signals, buffer);
1477 gbp_omni_gutter_renderer_reload (self);
1478 }
1479 }
1480
1481 static void
gbp_omni_gutter_renderer_bind_view(GbpOmniGutterRenderer * self,IdeSourceView * view,DzlSignalGroup * view_signals)1482 gbp_omni_gutter_renderer_bind_view (GbpOmniGutterRenderer *self,
1483 IdeSourceView *view,
1484 DzlSignalGroup *view_signals)
1485 {
1486 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1487 g_assert (IDE_IS_SOURCE_VIEW (view));
1488 g_assert (DZL_IS_SIGNAL_GROUP (view_signals));
1489
1490 gbp_omni_gutter_renderer_notify_buffer (self, NULL, view);
1491 }
1492
1493 static void
gbp_omni_gutter_renderer_notify_view(GbpOmniGutterRenderer * self)1494 gbp_omni_gutter_renderer_notify_view (GbpOmniGutterRenderer *self)
1495 {
1496 GtkTextView *view;
1497
1498 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1499
1500 view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (self));
1501 if (!IDE_IS_SOURCE_VIEW (view))
1502 view = NULL;
1503
1504 dzl_signal_group_set_target (self->view_signals, view);
1505 }
1506
1507 static gboolean
gbp_omni_gutter_renderer_do_recalc(gpointer data)1508 gbp_omni_gutter_renderer_do_recalc (gpointer data)
1509 {
1510 GbpOmniGutterRenderer *self = data;
1511
1512 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1513
1514 self->resize_source = 0;
1515
1516 gbp_omni_gutter_renderer_recalculate_size (self);
1517
1518 return G_SOURCE_REMOVE;
1519 }
1520
1521 static void
gbp_omni_gutter_renderer_buffer_changed(GbpOmniGutterRenderer * self,IdeBuffer * buffer)1522 gbp_omni_gutter_renderer_buffer_changed (GbpOmniGutterRenderer *self,
1523 IdeBuffer *buffer)
1524 {
1525 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1526 g_assert (IDE_IS_BUFFER (buffer));
1527
1528 /* Run immediately at the end of this main loop iteration */
1529 if (self->resize_source == 0)
1530 self->resize_source = gdk_threads_add_idle_full (G_PRIORITY_HIGH,
1531 gbp_omni_gutter_renderer_do_recalc,
1532 g_object_ref (self),
1533 g_object_unref);
1534 }
1535
1536 static void
gbp_omni_gutter_renderer_cursor_moved(GbpOmniGutterRenderer * self,const GtkTextIter * iter,GtkTextBuffer * buffer)1537 gbp_omni_gutter_renderer_cursor_moved (GbpOmniGutterRenderer *self,
1538 const GtkTextIter *iter,
1539 GtkTextBuffer *buffer)
1540 {
1541 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1542
1543 if (self->show_relative_line_numbers)
1544 gtk_source_gutter_renderer_queue_draw (GTK_SOURCE_GUTTER_RENDERER (self));
1545 }
1546
1547 static void
gbp_omni_gutter_renderer_notify_style_scheme(GbpOmniGutterRenderer * self,GParamSpec * pspec,IdeBuffer * buffer)1548 gbp_omni_gutter_renderer_notify_style_scheme (GbpOmniGutterRenderer *self,
1549 GParamSpec *pspec,
1550 IdeBuffer *buffer)
1551 {
1552 GtkSourceStyleScheme *scheme;
1553
1554 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1555 g_assert (IDE_IS_BUFFER (buffer));
1556
1557 /* Update our cached rgba colors */
1558 scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
1559 reload_style_colors (self, scheme);
1560
1561 /* Regenerate icons matching the scheme colors */
1562 gbp_omni_gutter_renderer_reload_icons (self);
1563 }
1564
1565 static void
gbp_omni_gutter_renderer_bind_buffer(GbpOmniGutterRenderer * self,IdeBuffer * buffer,DzlSignalGroup * buffer_signals)1566 gbp_omni_gutter_renderer_bind_buffer (GbpOmniGutterRenderer *self,
1567 IdeBuffer *buffer,
1568 DzlSignalGroup *buffer_signals)
1569 {
1570 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1571 g_assert (IDE_IS_BUFFER (buffer));
1572 g_assert (DZL_IS_SIGNAL_GROUP (buffer_signals));
1573
1574 gbp_omni_gutter_renderer_notify_style_scheme (self, NULL, buffer);
1575 }
1576
1577 static void
gbp_omni_gutter_renderer_constructed(GObject * object)1578 gbp_omni_gutter_renderer_constructed (GObject *object)
1579 {
1580 GbpOmniGutterRenderer *self = (GbpOmniGutterRenderer *)object;
1581 GtkTextView *view;
1582
1583 g_assert (GBP_IS_OMNI_GUTTER_RENDERER (self));
1584
1585 G_OBJECT_CLASS (gbp_omni_gutter_renderer_parent_class)->constructed (object);
1586
1587 view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (self));
1588 dzl_signal_group_set_target (self->view_signals, view);
1589
1590 self->settings = g_settings_new ("org.gnome.builder.editor");
1591 }
1592
1593 static void
gbp_omni_gutter_renderer_dispose(GObject * object)1594 gbp_omni_gutter_renderer_dispose (GObject *object)
1595 {
1596 GbpOmniGutterRenderer *self = (GbpOmniGutterRenderer *)object;
1597
1598 dzl_clear_source (&self->resize_source);
1599
1600 g_clear_object (&self->settings);
1601 g_clear_object (&self->breakpoints);
1602 g_clear_pointer (&self->lines, g_array_unref);
1603
1604 g_clear_pointer (&self->scaled_font_desc, pango_font_description_free);
1605
1606 g_clear_object (&self->view_signals);
1607 g_clear_object (&self->buffer_signals);
1608
1609 g_clear_pointer (&self->note_surface, cairo_surface_destroy);
1610 g_clear_pointer (&self->warning_surface, cairo_surface_destroy);
1611 g_clear_pointer (&self->error_surface, cairo_surface_destroy);
1612 g_clear_pointer (&self->note_selected_surface, cairo_surface_destroy);
1613 g_clear_pointer (&self->warning_selected_surface, cairo_surface_destroy);
1614 g_clear_pointer (&self->error_selected_surface, cairo_surface_destroy);
1615
1616 g_clear_object (&self->layout);
1617 g_clear_pointer (&self->bold_attrs, pango_attr_list_unref);
1618
1619 G_OBJECT_CLASS (gbp_omni_gutter_renderer_parent_class)->dispose (object);
1620 }
1621
1622 static void
gbp_omni_gutter_renderer_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1623 gbp_omni_gutter_renderer_get_property (GObject *object,
1624 guint prop_id,
1625 GValue *value,
1626 GParamSpec *pspec)
1627 {
1628 GbpOmniGutterRenderer *self = GBP_OMNI_GUTTER_RENDERER (object);
1629
1630 switch (prop_id)
1631 {
1632 case PROP_SHOW_LINE_CHANGES:
1633 g_value_set_boolean (value, self->show_line_changes);
1634 break;
1635
1636 case PROP_SHOW_LINE_DIAGNOSTICS:
1637 g_value_set_boolean (value, self->show_line_diagnostics);
1638 break;
1639
1640 case PROP_SHOW_LINE_NUMBERS:
1641 g_value_set_boolean (value, self->show_line_numbers);
1642 break;
1643
1644 case PROP_SHOW_RELATIVE_LINE_NUMBERS:
1645 g_value_set_boolean (value, self->show_relative_line_numbers);
1646 break;
1647
1648 default:
1649 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1650 }
1651 }
1652
1653 static void
gbp_omni_gutter_renderer_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1654 gbp_omni_gutter_renderer_set_property (GObject *object,
1655 guint prop_id,
1656 const GValue *value,
1657 GParamSpec *pspec)
1658 {
1659 GbpOmniGutterRenderer *self = GBP_OMNI_GUTTER_RENDERER (object);
1660
1661 switch (prop_id)
1662 {
1663 case PROP_SHOW_LINE_CHANGES:
1664 gbp_omni_gutter_renderer_set_show_line_changes (self, g_value_get_boolean (value));
1665 break;
1666
1667 case PROP_SHOW_LINE_DIAGNOSTICS:
1668 gbp_omni_gutter_renderer_set_show_line_diagnostics (self, g_value_get_boolean (value));
1669 break;
1670
1671 case PROP_SHOW_LINE_NUMBERS:
1672 gbp_omni_gutter_renderer_set_show_line_numbers (self, g_value_get_boolean (value));
1673 break;
1674
1675 case PROP_SHOW_RELATIVE_LINE_NUMBERS:
1676 gbp_omni_gutter_renderer_set_show_relative_line_numbers (self, g_value_get_boolean (value));
1677 break;
1678
1679 default:
1680 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1681 }
1682 }
1683
1684 static void
gbp_omni_gutter_renderer_class_init(GbpOmniGutterRendererClass * klass)1685 gbp_omni_gutter_renderer_class_init (GbpOmniGutterRendererClass *klass)
1686 {
1687 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1688 GtkSourceGutterRendererClass *renderer_class = GTK_SOURCE_GUTTER_RENDERER_CLASS (klass);
1689
1690 object_class->constructed = gbp_omni_gutter_renderer_constructed;
1691 object_class->dispose = gbp_omni_gutter_renderer_dispose;
1692 object_class->get_property = gbp_omni_gutter_renderer_get_property;
1693 object_class->set_property = gbp_omni_gutter_renderer_set_property;
1694
1695 renderer_class->draw = gbp_omni_gutter_renderer_draw;
1696 renderer_class->begin = gbp_omni_gutter_renderer_begin;
1697 renderer_class->end = gbp_omni_gutter_renderer_end;
1698 renderer_class->query_activatable = gbp_omni_gutter_renderer_query_activatable;
1699 renderer_class->activate = gbp_omni_gutter_renderer_activate;
1700
1701 properties [PROP_SHOW_LINE_CHANGES] =
1702 g_param_spec_boolean ("show-line-changes", NULL, NULL, TRUE,
1703 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1704
1705 properties [PROP_SHOW_LINE_NUMBERS] =
1706 g_param_spec_boolean ("show-line-numbers", NULL, NULL, TRUE,
1707 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1708
1709 properties [PROP_SHOW_RELATIVE_LINE_NUMBERS] =
1710 g_param_spec_boolean ("show-relative-line-numbers", NULL, NULL, FALSE,
1711 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1712
1713 properties [PROP_SHOW_LINE_DIAGNOSTICS] =
1714 g_param_spec_boolean ("show-line-diagnostics", NULL, NULL, TRUE,
1715 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1716
1717 g_object_class_install_properties (object_class, N_PROPS, properties);
1718 }
1719
1720 static void
gbp_omni_gutter_renderer_init(GbpOmniGutterRenderer * self)1721 gbp_omni_gutter_renderer_init (GbpOmniGutterRenderer *self)
1722 {
1723 self->diag_size = 16;
1724 self->show_line_changes = TRUE;
1725 self->show_line_diagnostics = TRUE;
1726 self->show_line_diagnostics = TRUE;
1727
1728 self->lines = g_array_new (FALSE, FALSE, sizeof (LineInfo));
1729
1730 g_signal_connect (self,
1731 "notify::view",
1732 G_CALLBACK (gbp_omni_gutter_renderer_notify_view),
1733 NULL);
1734
1735 self->buffer_signals = dzl_signal_group_new (IDE_TYPE_BUFFER);
1736
1737 g_signal_connect_swapped (self->buffer_signals,
1738 "bind",
1739 G_CALLBACK (gbp_omni_gutter_renderer_bind_buffer),
1740 self);
1741
1742 dzl_signal_group_connect_swapped (self->buffer_signals,
1743 "notify::file",
1744 G_CALLBACK (gbp_omni_gutter_renderer_reload),
1745 self);
1746
1747 dzl_signal_group_connect_swapped (self->buffer_signals,
1748 "notify::language",
1749 G_CALLBACK (gbp_omni_gutter_renderer_reload),
1750 self);
1751
1752 dzl_signal_group_connect_swapped (self->buffer_signals,
1753 "notify::style-scheme",
1754 G_CALLBACK (gbp_omni_gutter_renderer_notify_style_scheme),
1755 self);
1756
1757 dzl_signal_group_connect_swapped (self->buffer_signals,
1758 "changed",
1759 G_CALLBACK (gbp_omni_gutter_renderer_buffer_changed),
1760 self);
1761
1762 dzl_signal_group_connect_swapped (self->buffer_signals,
1763 "cursor-moved",
1764 G_CALLBACK (gbp_omni_gutter_renderer_cursor_moved),
1765 self);
1766
1767 self->view_signals = dzl_signal_group_new (IDE_TYPE_SOURCE_VIEW);
1768
1769 g_signal_connect_swapped (self->view_signals,
1770 "bind",
1771 G_CALLBACK (gbp_omni_gutter_renderer_bind_view),
1772 self);
1773
1774 dzl_signal_group_connect_swapped (self->view_signals,
1775 "notify::buffer",
1776 G_CALLBACK (gbp_omni_gutter_renderer_notify_buffer),
1777 self);
1778
1779 dzl_signal_group_connect_swapped (self->view_signals,
1780 "notify::font-desc",
1781 G_CALLBACK (gbp_omni_gutter_renderer_notify_font_desc),
1782 self);
1783
1784 self->bold_attrs = pango_attr_list_new ();
1785 pango_attr_list_insert (self->bold_attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
1786 }
1787
1788 GbpOmniGutterRenderer *
gbp_omni_gutter_renderer_new(void)1789 gbp_omni_gutter_renderer_new (void)
1790 {
1791 return g_object_new (GBP_TYPE_OMNI_GUTTER_RENDERER, NULL);
1792 }
1793
1794 gboolean
gbp_omni_gutter_renderer_get_show_line_changes(GbpOmniGutterRenderer * self)1795 gbp_omni_gutter_renderer_get_show_line_changes (GbpOmniGutterRenderer *self)
1796 {
1797 g_return_val_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self), FALSE);
1798
1799 return self->show_line_changes;
1800 }
1801
1802 gboolean
gbp_omni_gutter_renderer_get_show_line_diagnostics(GbpOmniGutterRenderer * self)1803 gbp_omni_gutter_renderer_get_show_line_diagnostics (GbpOmniGutterRenderer *self)
1804 {
1805 g_return_val_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self), FALSE);
1806
1807 return self->show_line_diagnostics;
1808 }
1809
1810 gboolean
gbp_omni_gutter_renderer_get_show_line_numbers(GbpOmniGutterRenderer * self)1811 gbp_omni_gutter_renderer_get_show_line_numbers (GbpOmniGutterRenderer *self)
1812 {
1813 g_return_val_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self), FALSE);
1814
1815 return self->show_line_numbers;
1816 }
1817
1818 gboolean
gbp_omni_gutter_renderer_get_show_relative_line_numbers(GbpOmniGutterRenderer * self)1819 gbp_omni_gutter_renderer_get_show_relative_line_numbers (GbpOmniGutterRenderer *self)
1820 {
1821 g_return_val_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self), FALSE);
1822
1823 return self->show_relative_line_numbers;
1824 }
1825
1826 void
gbp_omni_gutter_renderer_set_show_line_changes(GbpOmniGutterRenderer * self,gboolean show_line_changes)1827 gbp_omni_gutter_renderer_set_show_line_changes (GbpOmniGutterRenderer *self,
1828 gboolean show_line_changes)
1829 {
1830 g_return_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self));
1831
1832 show_line_changes = !!show_line_changes;
1833
1834 if (show_line_changes != self->show_line_changes)
1835 {
1836 self->show_line_changes = show_line_changes;
1837 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_LINE_CHANGES]);
1838 gbp_omni_gutter_renderer_recalculate_size (self);
1839 }
1840 }
1841
1842 void
gbp_omni_gutter_renderer_set_show_line_diagnostics(GbpOmniGutterRenderer * self,gboolean show_line_diagnostics)1843 gbp_omni_gutter_renderer_set_show_line_diagnostics (GbpOmniGutterRenderer *self,
1844 gboolean show_line_diagnostics)
1845 {
1846 g_return_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self));
1847
1848 show_line_diagnostics = !!show_line_diagnostics;
1849
1850 if (show_line_diagnostics != self->show_line_diagnostics)
1851 {
1852 self->show_line_diagnostics = show_line_diagnostics;
1853 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_LINE_DIAGNOSTICS]);
1854 gbp_omni_gutter_renderer_recalculate_size (self);
1855 }
1856 }
1857
1858 void
gbp_omni_gutter_renderer_set_show_line_numbers(GbpOmniGutterRenderer * self,gboolean show_line_numbers)1859 gbp_omni_gutter_renderer_set_show_line_numbers (GbpOmniGutterRenderer *self,
1860 gboolean show_line_numbers)
1861 {
1862 g_return_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self));
1863
1864 show_line_numbers = !!show_line_numbers;
1865
1866 if (show_line_numbers != self->show_line_numbers)
1867 {
1868 self->show_line_numbers = show_line_numbers;
1869 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_LINE_NUMBERS]);
1870 gbp_omni_gutter_renderer_recalculate_size (self);
1871 }
1872 }
1873
1874 void
gbp_omni_gutter_renderer_set_show_relative_line_numbers(GbpOmniGutterRenderer * self,gboolean show_relative_line_numbers)1875 gbp_omni_gutter_renderer_set_show_relative_line_numbers (GbpOmniGutterRenderer *self,
1876 gboolean show_relative_line_numbers)
1877 {
1878 g_return_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self));
1879
1880 show_relative_line_numbers = !!show_relative_line_numbers;
1881
1882 if (show_relative_line_numbers != self->show_relative_line_numbers)
1883 {
1884 self->show_relative_line_numbers = show_relative_line_numbers;
1885 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_RELATIVE_LINE_NUMBERS]);
1886 gtk_source_gutter_renderer_queue_draw (GTK_SOURCE_GUTTER_RENDERER (self));
1887 }
1888 }
1889
1890 static void
gbp_omni_gutter_renderer_style_changed(IdeGutter * gutter)1891 gbp_omni_gutter_renderer_style_changed (IdeGutter *gutter)
1892 {
1893 GbpOmniGutterRenderer *self = (GbpOmniGutterRenderer *)gutter;
1894
1895 g_return_if_fail (GBP_IS_OMNI_GUTTER_RENDERER (self));
1896
1897 gbp_omni_gutter_renderer_recalculate_size (self);
1898 gbp_omni_gutter_renderer_reload_icons (self);
1899 }
1900
1901 static void
gutter_iface_init(IdeGutterInterface * iface)1902 gutter_iface_init (IdeGutterInterface *iface)
1903 {
1904 iface->style_changed = gbp_omni_gutter_renderer_style_changed;
1905 }
1906