1 /* gbp-buildui-config-view-addin.c
2  *
3  * Copyright 2018-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-buildui-config-view-addin"
22 
23 #include "config.h"
24 
25 #include <glib/gi18n.h>
26 #include <libide-foundry.h>
27 #include <libide-gui.h>
28 
29 #include "gbp-buildui-config-view-addin.h"
30 #include "gbp-buildui-runtime-categories.h"
31 #include "gbp-buildui-runtime-row.h"
32 
33 struct _GbpBuilduiConfigViewAddin
34 {
35   GObject parent_instance;
36 };
37 
38 static gboolean
treat_null_as_empty(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer user_data)39 treat_null_as_empty (GBinding     *binding,
40                      const GValue *from_value,
41                      GValue       *to_value,
42                      gpointer      user_data)
43 {
44   const gchar *str = g_value_get_string (from_value);
45   g_value_set_string (to_value, str ?: "");
46   return TRUE;
47 }
48 
49 static void
add_description_row(DzlPreferences * preferences,const gchar * page,const gchar * group,const gchar * title,const gchar * value,GtkWidget * value_widget)50 add_description_row (DzlPreferences *preferences,
51                      const gchar    *page,
52                      const gchar    *group,
53                      const gchar    *title,
54                      const gchar    *value,
55                      GtkWidget      *value_widget)
56 {
57   GtkWidget *widget;
58 
59   g_assert (IDE_IS_MAIN_THREAD ());
60   g_assert (DZL_IS_PREFERENCES (preferences));
61 
62   widget = g_object_new (GTK_TYPE_LABEL,
63                          "xalign", 0.0f,
64                          "label", title,
65                          "visible", TRUE,
66                          "margin-right", 12,
67                          NULL);
68   dzl_gtk_widget_add_style_class (widget, "dim-label");
69 
70   if (value_widget == NULL)
71     value_widget = g_object_new (GTK_TYPE_LABEL,
72                                  "hexpand", TRUE,
73                                  "label", value,
74                                  "xalign", 0.0f,
75                                  "visible", TRUE,
76                                  NULL);
77 
78   dzl_preferences_add_table_row (preferences, page, group, widget, value_widget, NULL);
79 }
80 
81 static GtkWidget *
create_stack_list_row(gpointer item,gpointer user_data)82 create_stack_list_row (gpointer item,
83                        gpointer user_data)
84 {
85   IdeConfig *config = user_data;
86   GtkWidget *row;
87 
88   g_assert (IDE_IS_MAIN_THREAD ());
89   g_assert (IDE_IS_CONFIG (config));
90 
91   if (IDE_IS_RUNTIME (item))
92     return gbp_buildui_runtime_row_new (item, config);
93 
94   row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
95                       "visible", TRUE,
96                       NULL);
97   g_object_set_data_full (G_OBJECT (row),
98                           "ITEM",
99                           g_object_ref (item),
100                           g_object_unref);
101 
102   if (GBP_IS_BUILDUI_RUNTIME_CATEGORIES (item))
103     {
104       const gchar *category = gbp_buildui_runtime_categories_get_name (item);
105       GtkWidget *label;
106 
107       label = g_object_new (GTK_TYPE_LABEL,
108                             "label", category,
109                             "margin", 10,
110                             "use-markup", TRUE,
111                             "visible", TRUE,
112                             "xalign", 0.0f,
113                             NULL);
114       gtk_container_add (GTK_CONTAINER (row), label);
115     }
116   else if (DZL_IS_LIST_MODEL_FILTER (item))
117     {
118       const gchar *category = g_object_get_data (item, "CATEGORY");
119       GtkWidget *label;
120 
121       label = g_object_new (GTK_TYPE_LABEL,
122                             "label", category,
123                             "margin", 10,
124                             "use-markup", TRUE,
125                             "visible", TRUE,
126                             "xalign", 0.0f,
127                             NULL);
128       gtk_container_add (GTK_CONTAINER (row), label);
129     }
130 
131   return row;
132 }
133 
134 static void
on_runtime_row_activated_cb(DzlStackList * stack_list,GtkListBoxRow * row,gpointer user_data)135 on_runtime_row_activated_cb (DzlStackList  *stack_list,
136                              GtkListBoxRow *row,
137                              gpointer       user_data)
138 {
139   IdeConfig *config = user_data;
140   gpointer item;
141 
142   g_assert (IDE_IS_MAIN_THREAD ());
143   g_assert (DZL_IS_STACK_LIST (stack_list));
144   g_assert (GTK_IS_LIST_BOX_ROW (row));
145   g_assert (IDE_IS_CONFIG (config));
146 
147   if (GBP_IS_BUILDUI_RUNTIME_ROW (row))
148     {
149       const gchar *id;
150 
151       id = gbp_buildui_runtime_row_get_id (GBP_BUILDUI_RUNTIME_ROW (row));
152       ide_config_set_runtime_id (config, id);
153 
154       {
155         GtkWidget *box;
156 
157         if ((box = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_LIST_BOX)))
158           gtk_list_box_unselect_all (GTK_LIST_BOX (box));
159       }
160 
161       return;
162     }
163 
164   item = g_object_get_data (G_OBJECT (row), "ITEM");
165   g_assert (G_IS_LIST_MODEL (item) || IDE_IS_RUNTIME (item));
166 
167   if (GBP_IS_BUILDUI_RUNTIME_CATEGORIES (item))
168     {
169       dzl_stack_list_push (stack_list,
170                            create_stack_list_row (item, config),
171                            G_LIST_MODEL (item),
172                            create_stack_list_row,
173                            g_object_ref (config),
174                            g_object_unref);
175     }
176   else if (G_IS_LIST_MODEL (item))
177     {
178       dzl_stack_list_push (stack_list,
179                            create_stack_list_row (item, config),
180                            G_LIST_MODEL (item),
181                            create_stack_list_row,
182                            g_object_ref (config),
183                            g_object_unref);
184     }
185 }
186 
187 static GtkWidget *
create_runtime_box(IdeConfig * config,IdeRuntimeManager * runtime_manager)188 create_runtime_box (IdeConfig  *config,
189                     IdeRuntimeManager *runtime_manager)
190 {
191   g_autoptr(GbpBuilduiRuntimeCategories) filter = NULL;
192   DzlStackList *stack;
193   const gchar *category;
194   IdeRuntime *runtime;
195   GtkWidget *header;
196   GtkWidget *frame;
197 
198   g_assert (IDE_IS_MAIN_THREAD ());
199   g_assert (IDE_IS_CONFIG (config));
200   g_assert (IDE_IS_RUNTIME_MANAGER (runtime_manager));
201 
202   filter = gbp_buildui_runtime_categories_new (runtime_manager, NULL);
203 
204   frame = g_object_new (GTK_TYPE_FRAME,
205                         "visible", TRUE,
206                         NULL);
207 
208   header = g_object_new (GTK_TYPE_LABEL,
209                          "label", _("All Runtimes"),
210                          "margin", 10,
211                          "visible", TRUE,
212                          "xalign", 0.0f,
213                          NULL);
214 
215   stack = g_object_new (DZL_TYPE_STACK_LIST,
216                         "visible", TRUE,
217                         NULL);
218   dzl_stack_list_push (stack,
219                        header,
220                        G_LIST_MODEL (filter),
221                        create_stack_list_row,
222                        g_object_ref (config),
223                        g_object_unref);
224   gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (stack));
225 
226   g_signal_connect_object (stack,
227                            "row-activated",
228                            G_CALLBACK (on_runtime_row_activated_cb),
229                            config,
230                            0);
231 
232   if ((runtime = ide_config_get_runtime (config)) &&
233       (category = ide_runtime_get_category (runtime)))
234     {
235       g_autoptr(GString) prefix = g_string_new (NULL);
236       g_auto(GStrv) parts = g_strsplit (category, "/", 0);
237 
238       for (guint i = 0; parts[i]; i++)
239         {
240           g_autoptr(GListModel) model = NULL;
241 
242           g_string_append (prefix, parts[i]);
243           if (parts[i+1])
244             g_string_append_c (prefix, '/');
245 
246           model = gbp_buildui_runtime_categories_create_child_model (filter, prefix->str);
247 
248           dzl_stack_list_push (stack,
249                                create_stack_list_row (model, config),
250                                model,
251                                create_stack_list_row,
252                                g_object_ref (config),
253                                g_object_unref);
254         }
255     }
256 
257   return GTK_WIDGET (frame);
258 }
259 
260 static void
notify_toolchain_id(IdeConfig * config,GParamSpec * pspec,GtkImage * image)261 notify_toolchain_id (IdeConfig *config,
262                      GParamSpec       *pspec,
263                      GtkImage         *image)
264 {
265   const gchar *toolchain_id;
266   const gchar *current;
267 
268   g_assert (IDE_IS_MAIN_THREAD ());
269   g_assert (IDE_IS_CONFIG (config));
270   g_assert (GTK_IS_IMAGE (image));
271 
272   toolchain_id = ide_config_get_toolchain_id (config);
273   current = g_object_get_data (G_OBJECT (image), "TOOLCHAIN_ID");
274 
275   gtk_widget_set_visible (GTK_WIDGET (image), ide_str_equal0 (toolchain_id, current));
276 }
277 
278 static GtkWidget *
create_toolchain_row(gpointer item,gpointer user_data)279 create_toolchain_row (gpointer item,
280                       gpointer user_data)
281 {
282   IdeToolchain *toolchain = item;
283   IdeConfig *config = user_data;
284   const gchar *toolchain_id;
285   GtkWidget *label;
286   GtkWidget *row;
287   GtkWidget *box;
288   GtkImage *image;
289 
290   g_assert (IDE_IS_MAIN_THREAD ());
291   g_assert (IDE_IS_TOOLCHAIN (toolchain));
292   g_assert (IDE_IS_CONFIG (config));
293 
294   toolchain_id = ide_toolchain_get_id (toolchain);
295 
296   row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
297                       "visible", TRUE,
298                       NULL);
299   g_object_set_data_full (G_OBJECT (row), "TOOLCHAIN_ID", g_strdup (toolchain_id), g_free);
300 
301   box = g_object_new (GTK_TYPE_BOX,
302                       "spacing", 6,
303                       "visible", TRUE,
304                       NULL);
305   gtk_container_add (GTK_CONTAINER (row), box);
306 
307   label = g_object_new (GTK_TYPE_LABEL,
308                         "label", ide_toolchain_get_display_name (toolchain),
309                         "visible", TRUE,
310                         "xalign", 0.0f,
311                         NULL);
312   gtk_container_add (GTK_CONTAINER (box), label);
313 
314   image = g_object_new (GTK_TYPE_IMAGE,
315                         "icon-name", "object-select-symbolic",
316                         "halign", GTK_ALIGN_START,
317                         "hexpand", TRUE,
318                         NULL);
319   g_object_set_data_full (G_OBJECT (image), "TOOLCHAIN_ID", g_strdup (toolchain_id), g_free);
320   gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (image));
321 
322   g_signal_connect_object (config,
323                            "notify::toolchain-id",
324                            G_CALLBACK (notify_toolchain_id),
325                            image,
326                            0);
327   notify_toolchain_id (config, NULL, image);
328 
329   return row;
330 }
331 
332 static void
on_toolchain_row_activated_cb(GtkListBox * list_box,GtkListBoxRow * row,IdeConfig * config)333 on_toolchain_row_activated_cb (GtkListBox       *list_box,
334                                GtkListBoxRow    *row,
335                                IdeConfig *config)
336 {
337   const gchar *toolchain_id;
338 
339   g_assert (IDE_IS_MAIN_THREAD ());
340   g_assert (GTK_IS_LIST_BOX (list_box));
341   g_assert (GTK_IS_LIST_BOX_ROW (row));
342   g_assert (IDE_IS_CONFIG (config));
343 
344   if ((toolchain_id = g_object_get_data (G_OBJECT (row), "TOOLCHAIN_ID")))
345     ide_config_set_toolchain_id (config, toolchain_id);
346 
347   gtk_list_box_unselect_all (list_box);
348 }
349 
350 static GtkWidget *
create_toolchain_box(IdeConfig * config,IdeToolchainManager * toolchain_manager)351 create_toolchain_box (IdeConfig    *config,
352                       IdeToolchainManager *toolchain_manager)
353 {
354   GtkScrolledWindow *scroller;
355   GtkListBox *list_box;
356 
357   g_assert (IDE_IS_MAIN_THREAD ());
358   g_assert (IDE_IS_CONFIG (config));
359   g_assert (IDE_IS_TOOLCHAIN_MANAGER (toolchain_manager));
360 
361   scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
362                            "propagate-natural-height", TRUE,
363                            "shadow-type", GTK_SHADOW_IN,
364                            "visible", TRUE,
365                            NULL);
366 
367   list_box = g_object_new (GTK_TYPE_LIST_BOX,
368                            "visible", TRUE,
369                            NULL);
370   g_signal_connect_object (list_box,
371                            "row-activated",
372                            G_CALLBACK (on_toolchain_row_activated_cb),
373                            config,
374                            0);
375   gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (list_box));
376 
377   gtk_list_box_bind_model (list_box,
378                            G_LIST_MODEL (toolchain_manager),
379                            create_toolchain_row,
380                            g_object_ref (config),
381                            g_object_unref);
382 
383   return GTK_WIDGET (scroller);
384 }
385 
386 static void
gbp_buildui_config_view_addin_load(IdeConfigViewAddin * addin,DzlPreferences * preferences,IdeConfig * config)387 gbp_buildui_config_view_addin_load (IdeConfigViewAddin *addin,
388                                     DzlPreferences     *preferences,
389                                     IdeConfig   *config)
390 {
391   IdeToolchainManager *toolchain_manager;
392   IdeRuntimeManager *runtime_manager;
393   g_autoptr(GFile) workdir = NULL;
394   IdeBuildSystem *build_system;
395   IdeEnvironment *environ_;
396   IdeContext *context;
397   GtkWidget *box;
398   GtkWidget *entry;
399   static const struct {
400     const gchar *label;
401     const gchar *action;
402     const gchar *tooltip;
403     const gchar *style_class;
404   } actions[] = {
405     { N_("Make _Active"), "config-manager.current", N_("Select this configuration as the active configuration.") },
406     { N_("_Duplicate"), "config-manager.duplicate", N_("Duplicating the configuration allows making changes without modifying this configuration.") },
407     { N_("_Remove"), "config-manager.delete", N_("Removes the configuration and cannot be undone."), "destructive-action" },
408   };
409 
410   g_assert (IDE_IS_MAIN_THREAD ());
411   g_assert (GBP_IS_BUILDUI_CONFIG_VIEW_ADDIN (addin));
412   g_assert (DZL_IS_PREFERENCES (preferences));
413   g_assert (IDE_IS_CONFIG (config));
414 
415   /* Get manager objects */
416   context = ide_object_get_context (IDE_OBJECT (config));
417   runtime_manager = ide_runtime_manager_from_context (context);
418   toolchain_manager = ide_toolchain_manager_from_context (context);
419   build_system = ide_build_system_from_context (context);
420   workdir = ide_context_ref_workdir (context);
421 
422   /* Add our pages */
423   dzl_preferences_add_page (preferences, "general", _("General"), 0);
424   dzl_preferences_add_page (preferences, "environ", _("Environment"), 10);
425 
426   /* Add groups to pages */
427   dzl_preferences_add_list_group (preferences, "general", "general", _("Overview"), GTK_SELECTION_NONE, 0);
428   dzl_preferences_add_group (preferences, "general", "buttons", NULL, 0);
429   dzl_preferences_add_group (preferences, "environ", "build", _("Build Environment"), 0);
430   dzl_preferences_add_group (preferences, "environ", "runtime", _("Runtime Environment"), 0);
431 
432   /* actions button box */
433   box = g_object_new (GTK_TYPE_BOX,
434                       "homogeneous", TRUE,
435                       "spacing", 12,
436                       "visible", TRUE,
437                       NULL);
438   for (guint i = 0; i < G_N_ELEMENTS (actions); i++)
439     {
440       GtkWidget *button;
441 
442       button = g_object_new (GTK_TYPE_BUTTON,
443                              "visible", TRUE,
444                              "action-name", actions[i].action,
445                              "action-target", g_variant_new_string (ide_config_get_id (config)),
446                              "label", g_dgettext (GETTEXT_PACKAGE, actions[i].label),
447                              "tooltip-text", g_dgettext (GETTEXT_PACKAGE, actions[i].tooltip),
448                              "use-underline", TRUE,
449                              NULL);
450       if (actions[i].style_class)
451         dzl_gtk_widget_add_style_class (button, actions[i].style_class);
452       gtk_container_add (GTK_CONTAINER (box), button);
453     }
454 
455   /* Add description info */
456   add_description_row (preferences, "general", "general", _("Name"), ide_config_get_display_name (config), NULL);
457   add_description_row (preferences, "general", "general", _("Source Directory"), g_file_peek_path (workdir), NULL);
458   add_description_row (preferences, "general", "general", _("Build System"), ide_build_system_get_display_name (build_system), NULL);
459 
460   entry = g_object_new (GTK_TYPE_ENTRY,
461                         "visible", TRUE,
462                         "hexpand", TRUE,
463                         NULL);
464   g_object_bind_property_full (config, "prefix", entry, "text",
465                                G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
466                                treat_null_as_empty, NULL, NULL, NULL);
467   add_description_row (preferences, "general", "general", _("Install Prefix"), NULL, entry);
468 
469   entry = g_object_new (GTK_TYPE_ENTRY,
470                         "visible", TRUE,
471                         "hexpand", TRUE,
472                         NULL);
473   g_object_bind_property_full (config, "config-opts", entry, "text",
474                                G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
475                                treat_null_as_empty, NULL, NULL, NULL);
476   add_description_row (preferences, "general", "general", _("Configure Options"), NULL, entry);
477 
478   dzl_preferences_add_custom (preferences, "general", "buttons", box, NULL, 5);
479 
480   /* Setup runtime selection */
481   dzl_preferences_add_group (preferences, "general", "runtime", _("Application Runtime"), 10);
482   dzl_preferences_add_custom (preferences, "general", "runtime", create_runtime_box (config, runtime_manager), NULL, 10);
483 
484   /* Setup toolchain selection */
485   dzl_preferences_add_group (preferences, "general", "toolchain", _("Build Toolchain"), 20);
486   dzl_preferences_add_custom (preferences, "general", "toolchain", create_toolchain_box (config, toolchain_manager), NULL, 10);
487 
488   /* Add environment selector */
489   environ_ = ide_config_get_environment (config);
490   dzl_preferences_add_custom (preferences, "environ", "build",
491                               g_object_new (GTK_TYPE_FRAME,
492                                             "visible", TRUE,
493                                             "child", g_object_new (IDE_TYPE_ENVIRONMENT_EDITOR,
494                                                                    "environment", environ_,
495                                                                    "visible", TRUE,
496                                                                    NULL),
497                                             NULL),
498                               NULL, 0);
499 
500   /* Add runtime environment selector */
501   environ_ = ide_config_get_runtime_environment (config);
502   dzl_preferences_add_custom (preferences, "environ", "runtime",
503                               g_object_new (GTK_TYPE_FRAME,
504                                             "visible", TRUE,
505                                             "child", g_object_new (IDE_TYPE_ENVIRONMENT_EDITOR,
506                                                                    "environment", environ_,
507                                                                    "visible", TRUE,
508                                                                    NULL),
509                                             NULL),
510                               NULL, 0);
511 
512 }
513 
514 static void
config_view_addin_iface_init(IdeConfigViewAddinInterface * iface)515 config_view_addin_iface_init (IdeConfigViewAddinInterface *iface)
516 {
517   iface->load = gbp_buildui_config_view_addin_load;
518 }
519 
G_DEFINE_FINAL_TYPE_WITH_CODE(GbpBuilduiConfigViewAddin,gbp_buildui_config_view_addin,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (IDE_TYPE_CONFIG_VIEW_ADDIN,config_view_addin_iface_init))520 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpBuilduiConfigViewAddin, gbp_buildui_config_view_addin, G_TYPE_OBJECT,
521                          G_IMPLEMENT_INTERFACE (IDE_TYPE_CONFIG_VIEW_ADDIN, config_view_addin_iface_init))
522 
523 static void
524 gbp_buildui_config_view_addin_class_init (GbpBuilduiConfigViewAddinClass *klass)
525 {
526 }
527 
528 static void
gbp_buildui_config_view_addin_init(GbpBuilduiConfigViewAddin * self)529 gbp_buildui_config_view_addin_init (GbpBuilduiConfigViewAddin *self)
530 {
531 }
532