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