1 /* ide-context.c
2 *
3 * Copyright 2014-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 "ide-context"
22
23 #include "config.h"
24
25 #include <glib/gi18n.h>
26 #include <libpeas/peas.h>
27
28 #include "ide-context.h"
29 #include "ide-context-private.h"
30 #include "ide-context-addin.h"
31 #include "ide-macros.h"
32 #include "ide-notifications.h"
33
34 /**
35 * SECTION:ide-context
36 * @title: IdeContext
37 * @short_description: the root object for a project
38 *
39 * The #IdeContext object is the root object for a project. Everything
40 * in a project is contained by this object.
41 *
42 * Since: 3.32
43 */
44
45 struct _IdeContext
46 {
47 IdeObject parent_instance;
48 PeasExtensionSet *addins;
49 gchar *project_id;
50 gchar *title;
51 GFile *workdir;
52 guint project_loaded : 1;
53 };
54
55 enum {
56 PROP_0,
57 PROP_PROJECT_ID,
58 PROP_TITLE,
59 PROP_WORKDIR,
60 N_PROPS
61 };
62
63 enum {
64 LOG,
65 N_SIGNALS
66 };
67
G_DEFINE_FINAL_TYPE(IdeContext,ide_context,IDE_TYPE_OBJECT)68 G_DEFINE_FINAL_TYPE (IdeContext, ide_context, IDE_TYPE_OBJECT)
69
70 static GParamSpec *properties [N_PROPS];
71 static guint signals [N_SIGNALS];
72
73 static void
74 ide_context_addin_load_project_cb (GObject *object,
75 GAsyncResult *result,
76 gpointer user_data)
77 {
78 IdeContextAddin *addin = (IdeContextAddin *)object;
79 g_autoptr(IdeContext) self = user_data;
80 g_autoptr(GError) error = NULL;
81
82 g_assert (IDE_IS_CONTEXT_ADDIN (addin));
83 g_assert (G_IS_ASYNC_RESULT (result));
84 g_assert (IDE_IS_CONTEXT (self));
85
86 if (ide_context_addin_load_project_finish (addin, result, &error))
87 ide_context_addin_project_loaded (addin, self);
88 else
89 g_warning ("%s context addin failed to load project: %s",
90 G_OBJECT_TYPE_NAME (addin), error->message);
91 }
92
93 static void
ide_context_addin_added_cb(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)94 ide_context_addin_added_cb (PeasExtensionSet *set,
95 PeasPluginInfo *plugin_info,
96 PeasExtension *exten,
97 gpointer user_data)
98 {
99 IdeContextAddin *addin = (IdeContextAddin *)exten;
100 IdeContext *self = user_data;
101 g_autoptr(GCancellable) cancellable = NULL;
102
103 g_assert (PEAS_IS_EXTENSION_SET (set));
104 g_assert (plugin_info != NULL);
105 g_assert (IDE_IS_CONTEXT_ADDIN (addin));
106
107 /* Ignore any request during shutdown */
108 cancellable = ide_object_ref_cancellable (IDE_OBJECT (self));
109 if (g_cancellable_is_cancelled (cancellable))
110 return;
111
112 ide_context_addin_load (addin, self);
113
114 if (self->project_loaded)
115 ide_context_addin_load_project_async (addin,
116 self,
117 cancellable,
118 ide_context_addin_load_project_cb,
119 g_object_ref (self));
120 }
121
122 static void
ide_context_addin_removed_cb(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)123 ide_context_addin_removed_cb (PeasExtensionSet *set,
124 PeasPluginInfo *plugin_info,
125 PeasExtension *exten,
126 gpointer user_data)
127 {
128 IdeContextAddin *addin = (IdeContextAddin *)exten;
129 IdeContext *self = user_data;
130
131 g_assert (PEAS_IS_EXTENSION_SET (set));
132 g_assert (plugin_info != NULL);
133 g_assert (IDE_IS_CONTEXT_ADDIN (addin));
134
135 ide_context_addin_unload (addin, self);
136 }
137
138 static void
ide_context_real_log(IdeContext * self,GLogLevelFlags level,const gchar * domain,const gchar * message)139 ide_context_real_log (IdeContext *self,
140 GLogLevelFlags level,
141 const gchar *domain,
142 const gchar *message)
143 {
144 g_log (domain, level, "%s", message);
145 }
146
147 static gchar *
ide_context_repr(IdeObject * object)148 ide_context_repr (IdeObject *object)
149 {
150 IdeContext *self = IDE_CONTEXT (object);
151
152 return g_strdup_printf ("%s workdir=\"%s\" has_project=%d",
153 G_OBJECT_TYPE_NAME (self),
154 g_file_peek_path (self->workdir),
155 self->project_loaded);
156 }
157
158 static void
ide_context_constructed(GObject * object)159 ide_context_constructed (GObject *object)
160 {
161 IdeContext *self = (IdeContext *)object;
162
163 g_assert (IDE_IS_OBJECT (object));
164
165 self->addins = peas_extension_set_new (peas_engine_get_default (),
166 IDE_TYPE_CONTEXT_ADDIN,
167 NULL);
168
169 g_signal_connect (self->addins,
170 "extension-added",
171 G_CALLBACK (ide_context_addin_added_cb),
172 self);
173
174 g_signal_connect (self->addins,
175 "extension-removed",
176 G_CALLBACK (ide_context_addin_removed_cb),
177 self);
178
179 peas_extension_set_foreach (self->addins,
180 ide_context_addin_added_cb,
181 self);
182
183 G_OBJECT_CLASS (ide_context_parent_class)->constructed (object);
184 }
185
186 static void
ide_context_destroy(IdeObject * object)187 ide_context_destroy (IdeObject *object)
188 {
189 IdeContext *self = (IdeContext *)object;
190
191 g_assert (IDE_IS_OBJECT (object));
192
193 g_clear_object (&self->addins);
194
195 IDE_OBJECT_CLASS (ide_context_parent_class)->destroy (object);
196 }
197
198 static void
ide_context_finalize(GObject * object)199 ide_context_finalize (GObject *object)
200 {
201 IdeContext *self = (IdeContext *)object;
202
203 g_clear_object (&self->workdir);
204 g_clear_pointer (&self->project_id, g_free);
205 g_clear_pointer (&self->title, g_free);
206
207 G_OBJECT_CLASS (ide_context_parent_class)->finalize (object);
208 }
209
210 static void
ide_context_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)211 ide_context_get_property (GObject *object,
212 guint prop_id,
213 GValue *value,
214 GParamSpec *pspec)
215 {
216 IdeContext *self = IDE_CONTEXT (object);
217
218 switch (prop_id)
219 {
220 case PROP_PROJECT_ID:
221 g_value_take_string (value, ide_context_dup_project_id (self));
222 break;
223
224 case PROP_TITLE:
225 g_value_take_string (value, ide_context_dup_title (self));
226 break;
227
228 case PROP_WORKDIR:
229 g_value_take_object (value, ide_context_ref_workdir (self));
230 break;
231
232 default:
233 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
234 }
235 }
236
237 static void
ide_context_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)238 ide_context_set_property (GObject *object,
239 guint prop_id,
240 const GValue *value,
241 GParamSpec *pspec)
242 {
243 IdeContext *self = IDE_CONTEXT (object);
244
245 switch (prop_id)
246 {
247 case PROP_PROJECT_ID:
248 ide_context_set_project_id (self, g_value_get_string (value));
249 break;
250
251 case PROP_TITLE:
252 ide_context_set_title (self, g_value_get_string (value));
253 break;
254
255 case PROP_WORKDIR:
256 ide_context_set_workdir (self, g_value_get_object (value));
257 break;
258
259 default:
260 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
261 }
262 }
263
264 static void
ide_context_class_init(IdeContextClass * klass)265 ide_context_class_init (IdeContextClass *klass)
266 {
267 GObjectClass *object_class = G_OBJECT_CLASS (klass);
268 IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
269
270 object_class->constructed = ide_context_constructed;
271 object_class->finalize = ide_context_finalize;
272 object_class->get_property = ide_context_get_property;
273 object_class->set_property = ide_context_set_property;
274
275 i_object_class->destroy = ide_context_destroy;
276 i_object_class->repr = ide_context_repr;
277
278 /**
279 * IdeContext:project-id:
280 *
281 * The "project-id" property is the identifier to use when creating
282 * files and folders for this project. It has a mutated form of either
283 * the directory or some other discoverable trait of the project.
284 *
285 * It has also been modified to remove spaces and other unsafe
286 * characters for file-systems.
287 *
288 * This may change during runtime, but usually only once when the
289 * project has been initialize loaded.
290 *
291 * Before any project has loaded, this is "empty" to allow flexibility
292 * for non-project use.
293 *
294 * Since: 3.32
295 */
296 properties [PROP_PROJECT_ID] =
297 g_param_spec_string ("project-id",
298 "Project Id",
299 "The project identifier used when creating files and folders",
300 "empty",
301 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
302
303 /**
304 * IdeContext:title:
305 *
306 * The "title" property is a descriptive name for the project.
307 *
308 * Since: 3.32
309 */
310 properties [PROP_TITLE] =
311 g_param_spec_string ("title",
312 "Title",
313 "The title of the project",
314 NULL,
315 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
316
317 /**
318 * IdeContext:workdir:
319 *
320 * The "workdir" property is the best guess at the working directory for the
321 * context. This may be discovered using a common parent if multiple files
322 * are opened without a project.
323 *
324 * Since: 3.32
325 */
326 properties [PROP_WORKDIR] =
327 g_param_spec_object ("workdir",
328 "Working Directory",
329 "The working directory for the project",
330 G_TYPE_FILE,
331 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
332
333 g_object_class_install_properties (object_class, N_PROPS, properties);
334
335 /**
336 * IdeContext::log:
337 * @self: an #IdeContext
338 * @severity: the log severity
339 * @domain: the log domain
340 * @message: the log message
341 *
342 * This signal is emitted when a log item has been added for the context.
343 *
344 * Since: 3.32
345 */
346 signals [LOG] =
347 g_signal_new_class_handler ("log",
348 G_TYPE_FROM_CLASS (klass),
349 G_SIGNAL_RUN_LAST,
350 G_CALLBACK (ide_context_real_log),
351 NULL, NULL, NULL,
352 G_TYPE_NONE,
353 3,
354 G_TYPE_UINT,
355 G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
356 G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
357 }
358
359 static void
ide_context_init(IdeContext * self)360 ide_context_init (IdeContext *self)
361 {
362 g_autoptr(IdeNotifications) notifs = NULL;
363
364 self->workdir = g_file_new_for_path (g_get_home_dir ());
365 self->project_id = g_strdup ("empty");
366 self->title = g_strdup (_("Untitled"));
367
368 notifs = ide_notifications_new ();
369 ide_object_append (IDE_OBJECT (self), IDE_OBJECT (notifs));
370 }
371
372 /**
373 * ide_context_new:
374 *
375 * Creates a new #IdeContext.
376 *
377 * This only creates the context object. After creating the object you need
378 * to set a number of properties and then initialize asynchronously using
379 * g_async_initable_init_async().
380 *
381 * Returns: (transfer full): an #IdeContext
382 *
383 * Since: 3.32
384 */
385 IdeContext *
ide_context_new(void)386 ide_context_new (void)
387 {
388 return ide_object_new (IDE_TYPE_CONTEXT, NULL);
389 }
390
391 static void
ide_context_peek_child_typed_cb(IdeObject * object,gpointer user_data)392 ide_context_peek_child_typed_cb (IdeObject *object,
393 gpointer user_data)
394 {
395 struct {
396 IdeObject *ret;
397 GType type;
398 } *lookup = user_data;
399
400 g_assert (IDE_IS_MAIN_THREAD ());
401
402 if (lookup->ret != NULL)
403 return;
404
405 /* Take a borrowed instance, we're in the main thread so
406 * we can ensure it's not fully destroyed.
407 */
408 if (G_TYPE_CHECK_INSTANCE_TYPE (object, lookup->type))
409 lookup->ret = object;
410 }
411
412 /**
413 * ide_context_peek_child_typed:
414 * @self: a #IdeContext
415 * @type: the #GType of the child
416 *
417 * Looks for the first child matching @type, and returns it. No reference is
418 * taken to the child, so you should avoid using this except as used by
419 * compatability functions.
420 *
421 * This may only be called from the main thread or you risk the objects
422 * being finalized before your caller has a chance to reference them.
423 *
424 * Returns: (transfer none) (type IdeObject) (nullable): an #IdeObject that
425 * matches @type if successful; otherwise %NULL
426 *
427 * Since: 3.32
428 */
429 gpointer
ide_context_peek_child_typed(IdeContext * self,GType type)430 ide_context_peek_child_typed (IdeContext *self,
431 GType type)
432 {
433 struct {
434 IdeObject *ret;
435 GType type;
436 } lookup = { NULL, type };
437
438 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
439 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
440
441 ide_object_lock (IDE_OBJECT (self));
442 ide_object_foreach (IDE_OBJECT (self), (GFunc)ide_context_peek_child_typed_cb, &lookup);
443 ide_object_unlock (IDE_OBJECT (self));
444
445 return lookup.ret;
446 }
447
448 /**
449 * ide_context_dup_project_id:
450 * @self: a #IdeContext
451 *
452 * Copies the project-id and returns it to the caller.
453 *
454 * Returns: (transfer full): a project-id as a string
455 *
456 * Since: 3.32
457 */
458 gchar *
ide_context_dup_project_id(IdeContext * self)459 ide_context_dup_project_id (IdeContext *self)
460 {
461 gchar *ret;
462
463 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
464
465 ide_object_lock (IDE_OBJECT (self));
466 ret = g_strdup (self->project_id);
467 ide_object_unlock (IDE_OBJECT (self));
468
469 g_return_val_if_fail (ret != NULL, NULL);
470
471 return g_steal_pointer (&ret);
472 }
473
474 /**
475 * ide_context_set_project_id:
476 * @self: a #IdeContext
477 *
478 * Sets the project-id for the context.
479 *
480 * Generally, this should only be done once after loading a project.
481 *
482 * Since: 3.32
483 */
484 void
ide_context_set_project_id(IdeContext * self,const gchar * project_id)485 ide_context_set_project_id (IdeContext *self,
486 const gchar *project_id)
487 {
488 g_return_if_fail (IDE_IS_CONTEXT (self));
489
490 if (ide_str_empty0 (project_id))
491 project_id = "empty";
492
493 ide_object_lock (IDE_OBJECT (self));
494 if (!ide_str_equal0 (self->project_id, project_id))
495 {
496 g_free (self->project_id);
497 self->project_id = g_strdup (project_id);
498 ide_object_notify_by_pspec (IDE_OBJECT (self), properties [PROP_PROJECT_ID]);
499 }
500 ide_object_unlock (IDE_OBJECT (self));
501 }
502
503 /**
504 * ide_context_ref_workdir:
505 * @self: a #IdeContext
506 *
507 * Gets the working-directory of the context and increments the
508 * reference count by one.
509 *
510 * Returns: (transfer full): a #GFile
511 *
512 * Since: 3.32
513 */
514 GFile *
ide_context_ref_workdir(IdeContext * self)515 ide_context_ref_workdir (IdeContext *self)
516 {
517 GFile *ret;
518
519 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
520
521 ide_object_lock (IDE_OBJECT (self));
522 ret = g_object_ref (self->workdir);
523 ide_object_unlock (IDE_OBJECT (self));
524
525 return g_steal_pointer (&ret);
526 }
527
528 /**
529 * ide_context_set_workdir:
530 * @self: a #IdeContext
531 * @workdir: a #GFile
532 *
533 * Sets the working directory for the project.
534 *
535 * This should generally only be set once after checking out the project.
536 *
537 * In future releases, changes may be made to change this in support of
538 * git-worktrees or similar workflows.
539 *
540 * Since: 3.32
541 */
542 void
ide_context_set_workdir(IdeContext * self,GFile * workdir)543 ide_context_set_workdir (IdeContext *self,
544 GFile *workdir)
545 {
546 g_return_if_fail (IDE_IS_CONTEXT (self));
547 g_return_if_fail (G_IS_FILE (workdir));
548
549 ide_object_lock (IDE_OBJECT (self));
550 if (g_set_object (&self->workdir, workdir))
551 ide_object_notify_by_pspec (G_OBJECT (self), properties [PROP_WORKDIR]);
552 ide_object_unlock (IDE_OBJECT (self));
553 }
554
555 /**
556 * ide_context_cache_file:
557 * @self: a #IdeContext
558 * @first_part: (nullable): The first part of the path
559 *
560 * Like ide_context_cache_filename() but returns a #GFile.
561 *
562 * Returns: (transfer full): a #GFile for the cache file
563 *
564 * Since: 3.32
565 */
566 GFile *
ide_context_cache_file(IdeContext * self,const gchar * first_part,...)567 ide_context_cache_file (IdeContext *self,
568 const gchar *first_part,
569 ...)
570 {
571 g_autoptr(GPtrArray) ar = NULL;
572 g_autofree gchar *path = NULL;
573 g_autofree gchar *project_id = NULL;
574 const gchar *part = first_part;
575 va_list args;
576
577 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
578
579 project_id = ide_context_dup_project_id (self);
580
581 ar = g_ptr_array_new ();
582 g_ptr_array_add (ar, (gchar *)g_get_user_cache_dir ());
583 g_ptr_array_add (ar, (gchar *)ide_get_program_name ());
584 g_ptr_array_add (ar, (gchar *)"projects");
585 g_ptr_array_add (ar, (gchar *)project_id);
586
587 if (part != NULL)
588 {
589 va_start (args, first_part);
590 do
591 {
592 g_ptr_array_add (ar, (gchar *)part);
593 part = va_arg (args, const gchar *);
594 }
595 while (part != NULL);
596 va_end (args);
597 }
598
599 g_ptr_array_add (ar, NULL);
600
601 path = g_build_filenamev ((gchar **)ar->pdata);
602
603 return g_file_new_for_path (path);
604 }
605
606 /**
607 * ide_context_cache_filename:
608 * @self: a #IdeContext
609 * @first_part: the first part of the filename
610 *
611 * Creates a new filename that will be located in the projects cache directory.
612 * This makes it convenient to remove files when a project is deleted as all
613 * cache files will share a unified parent directory.
614 *
615 * The file will be located in a directory similar to
616 * ~/.cache/gnome-builder/project_name. This may change based on the value
617 * of g_get_user_cache_dir().
618 *
619 * Returns: (transfer full): A new string containing the cache filename
620 *
621 * Since: 3.32
622 */
623 gchar *
ide_context_cache_filename(IdeContext * self,const gchar * first_part,...)624 ide_context_cache_filename (IdeContext *self,
625 const gchar *first_part,
626 ...)
627 {
628 g_autofree gchar *project_id = NULL;
629 g_autofree gchar *base = NULL;
630 va_list args;
631 gchar *ret;
632
633 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
634
635 project_id = ide_context_dup_project_id (self);
636
637 g_return_val_if_fail (project_id != NULL, NULL);
638
639 base = g_build_filename (g_get_user_cache_dir (),
640 ide_get_program_name (),
641 "projects",
642 project_id,
643 first_part,
644 NULL);
645
646 if (first_part != NULL)
647 {
648 va_start (args, first_part);
649 ret = g_build_filename_valist (base, &args);
650 va_end (args);
651 }
652 else
653 {
654 ret = g_steal_pointer (&base);
655 }
656
657 return g_steal_pointer (&ret);
658 }
659
660 /**
661 * ide_context_build_file:
662 * @self: a #IdeContext
663 * @path: (nullable): a path to the file
664 *
665 * Creates a new #GFile for the path.
666 *
667 * - If @path is %NULL, #IdeContext:workdir is returned.
668 * - If @path is absolute, a new #GFile to the absolute path is returned.
669 * - Otherwise, a #GFile child of #IdeContext:workdir is returned.
670 *
671 * Returns: (transfer full): a #GFile
672 *
673 * Since: 3.32
674 */
675 GFile *
ide_context_build_file(IdeContext * self,const gchar * path)676 ide_context_build_file (IdeContext *self,
677 const gchar *path)
678 {
679 g_autoptr(GFile) ret = NULL;
680
681 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
682
683 if (path == NULL)
684 ret = g_file_dup (self->workdir);
685 else if (g_path_is_absolute (path))
686 ret = g_file_new_for_path (path);
687 else
688 ret = g_file_get_child (self->workdir, path);
689
690 g_debug ("Creating file \"%s\" from \"%s\"", g_file_peek_path (ret), path);
691
692 return g_steal_pointer (&ret);
693 }
694
695 /**
696 * ide_context_build_filename:
697 * @self: a #IdeContext
698 * @first_part: first path part
699 *
700 * Creates a new path that starts from the working directory of the
701 * loaded project.
702 *
703 * Returns: (transfer full): a string containing the new path
704 *
705 * Since: 3.32
706 */
707 gchar *
ide_context_build_filename(IdeContext * self,const gchar * first_part,...)708 ide_context_build_filename (IdeContext *self,
709 const gchar *first_part,
710 ...)
711 {
712 g_autoptr(GPtrArray) ar = NULL;
713 g_autoptr(GFile) workdir = NULL;
714 const gchar *part = first_part;
715 const gchar *base;
716 va_list args;
717
718 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
719 g_return_val_if_fail (first_part != NULL, NULL);
720
721 workdir = ide_context_ref_workdir (self);
722 base = g_file_peek_path (workdir);
723
724 ar = g_ptr_array_new ();
725
726 /* If first part is absolute, just use that as our root */
727 if (!g_path_is_absolute (first_part))
728 g_ptr_array_add (ar, (gchar *)base);
729
730 va_start (args, first_part);
731 do
732 {
733 g_ptr_array_add (ar, (gchar *)part);
734 part = va_arg (args, const gchar *);
735 }
736 while (part != NULL);
737 va_end (args);
738
739 g_ptr_array_add (ar, NULL);
740
741 return g_build_filenamev ((gchar **)ar->pdata);
742 }
743
744 /**
745 * ide_context_ref_project_settings:
746 * @self: a #IdeContext
747 *
748 * Gets an org.gnome.builder.project #GSettings.
749 *
750 * This creates a new #GSettings instance for the project.
751 *
752 * Returns: (transfer full): a #GSettings
753 *
754 * Since: 3.32
755 */
756 GSettings *
ide_context_ref_project_settings(IdeContext * self)757 ide_context_ref_project_settings (IdeContext *self)
758 {
759 g_autofree gchar *path = NULL;
760
761 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
762
763 ide_object_lock (IDE_OBJECT (self));
764 path = g_strdup_printf ("/org/gnome/builder/projects/%s/", self->project_id);
765 ide_object_unlock (IDE_OBJECT (self));
766
767 return g_settings_new_with_path ("org.gnome.builder.project", path);
768 }
769
770 /**
771 * ide_context_dup_title:
772 * @self: a #IdeContext
773 *
774 * Returns: (transfer full): a string containing the title
775 *
776 * Since: 3.32
777 */
778 gchar *
ide_context_dup_title(IdeContext * self)779 ide_context_dup_title (IdeContext *self)
780 {
781 gchar *ret;
782
783 g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
784
785 ide_object_lock (IDE_OBJECT (self));
786 ret = g_strdup (self->title);
787 ide_object_unlock (IDE_OBJECT (self));
788
789 return g_steal_pointer (&ret);
790 }
791
792 /**
793 * ide_context_set_title:
794 * @self: an #IdeContext
795 * @title: (nullable): the title for the project or %NULL
796 *
797 * Sets the #IdeContext:title property. This is used by various
798 * components to show the user the name of the project. This may
799 * include the omnibar and the window title.
800 *
801 * Since: 3.32
802 */
803 void
ide_context_set_title(IdeContext * self,const gchar * title)804 ide_context_set_title (IdeContext *self,
805 const gchar *title)
806 {
807 g_return_if_fail (IDE_IS_CONTEXT (self));
808
809 if (ide_str_empty0 (title))
810 title = _("Untitled");
811
812 ide_object_lock (IDE_OBJECT (self));
813 if (!ide_str_equal0 (self->title, title))
814 {
815 g_free (self->title);
816 self->title = g_strdup (title);
817 ide_object_notify_by_pspec (IDE_OBJECT (self), properties [PROP_TITLE]);
818 }
819 ide_object_unlock (IDE_OBJECT (self));
820 }
821
822 void
ide_context_log(IdeContext * self,GLogLevelFlags level,const gchar * domain,const gchar * message)823 ide_context_log (IdeContext *self,
824 GLogLevelFlags level,
825 const gchar *domain,
826 const gchar *message)
827 {
828 g_assert (IDE_IS_CONTEXT (self));
829
830 g_signal_emit (self, signals [LOG], 0, level, domain, message);
831 }
832
833 /**
834 * ide_context_has_project:
835 * @self: a #IdeContext
836 *
837 * Checks to see if a project has been loaded in @context.
838 *
839 * Returns: %TRUE if a project has been, or is currently, loading.
840 *
841 * Since: 3.32
842 */
843 gboolean
ide_context_has_project(IdeContext * self)844 ide_context_has_project (IdeContext *self)
845 {
846 gboolean ret;
847
848 g_return_val_if_fail (IDE_IS_CONTEXT (self), FALSE);
849
850 ide_object_lock (IDE_OBJECT (self));
851 ret = self->project_loaded;
852 ide_object_unlock (IDE_OBJECT (self));
853
854 return ret;
855 }
856
857 void
_ide_context_set_has_project(IdeContext * self)858 _ide_context_set_has_project (IdeContext *self)
859 {
860 g_return_if_fail (IDE_IS_CONTEXT (self));
861
862 ide_object_lock (IDE_OBJECT (self));
863 self->project_loaded = TRUE;
864 ide_object_unlock (IDE_OBJECT (self));
865 }
866
867 /**
868 * ide_context_addin_find_by_module_name:
869 * @context: an #IdeContext
870 * @module_name: the name of the addin module
871 *
872 * Finds the addin (if any) matching the plugin's @module_name.
873 *
874 * Returns: (transfer none) (nullable): an #IdeContextAddin or %NULL
875 *
876 * Since: 3.40
877 */
878 IdeContextAddin *
ide_context_addin_find_by_module_name(IdeContext * context,const gchar * module_name)879 ide_context_addin_find_by_module_name (IdeContext *context,
880 const gchar *module_name)
881 {
882 PeasPluginInfo *plugin_info;
883 PeasExtension *ret = NULL;
884 PeasEngine *engine;
885
886 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
887 g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
888 g_return_val_if_fail (module_name != NULL, NULL);
889
890 if (context->addins == NULL)
891 return NULL;
892
893 engine = peas_engine_get_default ();
894
895 if ((plugin_info = peas_engine_get_plugin_info (engine, module_name)))
896 ret = peas_extension_set_get_extension (context->addins, plugin_info);
897
898 return IDE_CONTEXT_ADDIN (ret);
899 }
900