/* ide-workspace.c * * Copyright 2014-2019 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-workspace" #include "config.h" #include #include "ide-gui-global.h" #include "ide-gui-private.h" #include "ide-workspace.h" #include "ide-workspace-addin.h" #define MUX_ACTIONS_KEY "IDE_WORKSPACE_MUX_ACTIONS" typedef struct { /* Used as a link in IdeWorkbench's GQueue to track the most-recently-used * workspaces based on recent focus. */ GList mru_link; /* This cancellable auto-cancels when the window is destroyed using * ::delete-event() so that async operations can be made to auto-cancel. */ GCancellable *cancellable; /* The context for our workbench. It may not have a project loaded until * the ide_workbench_load_project_async() workflow has been called, but it * is usable without a project (albeit restricted). */ IdeContext *context; /* Our addins for the workspace window, that are limited by the "kind" of * workspace that is loaded. Plugin files can specify X-Workspace-Kind to * limit the plugin to specific type(s) of workspace. */ IdeExtensionSetAdapter *addins; /* We use an overlay as our top-most child so that plugins can potentially * render any widget a layer above the UI. */ GtkOverlay *overlay; /* All workspaces are comprised of a series of "surfaces". However there may * only ever be a single surface in a workspace (such as the editor workspace * which is dedicated for editing). */ GtkStack *surfaces; /* The event box ensures that we can have events that will be used by the * fullscreen overlay so that it gets delivery of crossing events. */ GtkEventBox *event_box; GtkBox *vbox; /* A MRU that is updated as pages are focused. It allows us to move through * the pages in the order they've been most-recently focused. */ GQueue page_mru; guint in_key_press : 1; } IdeWorkspacePrivate; typedef struct { GtkCallback callback; gpointer user_data; } ForeachPage; enum { SURFACE_SET, N_SIGNALS }; enum { PROP_0, PROP_CONTEXT, PROP_VISIBLE_SURFACE, N_PROPS }; static void buildable_iface_init (GtkBuildableIface *iface); G_DEFINE_ABSTRACT_TYPE_WITH_CODE (IdeWorkspace, ide_workspace, HDY_TYPE_APPLICATION_WINDOW, G_ADD_PRIVATE (IdeWorkspace) G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init)) static GtkBuildableIface *parent_builder; static GParamSpec *properties [N_PROPS]; static guint signals [N_SIGNALS]; static void ide_workspace_addin_added_cb (IdeExtensionSetAdapter *set, PeasPluginInfo *plugin_info, PeasExtension *exten, gpointer user_data) { IdeWorkspaceAddin *addin = (IdeWorkspaceAddin *)exten; IdeWorkspace *self = user_data; g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set)); g_assert (plugin_info != NULL); g_assert (IDE_IS_WORKSPACE_ADDIN (addin)); g_assert (IDE_IS_WORKSPACE (self)); g_debug ("Loading workspace addin from module %s", peas_plugin_info_get_module_name (plugin_info)); ide_workspace_addin_load (addin, self); } static void ide_workspace_addin_removed_cb (IdeExtensionSetAdapter *set, PeasPluginInfo *plugin_info, PeasExtension *exten, gpointer user_data) { IdeWorkspaceAddin *addin = (IdeWorkspaceAddin *)exten; IdeWorkspace *self = user_data; g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set)); g_assert (plugin_info != NULL); g_assert (IDE_IS_WORKSPACE_ADDIN (addin)); g_assert (IDE_IS_WORKSPACE (self)); g_debug ("Unloading workspace addin from module %s", peas_plugin_info_get_module_name (plugin_info)); ide_workspace_addin_surface_set (addin, NULL); ide_workspace_addin_unload (addin, self); } static void ide_workspace_real_context_set (IdeWorkspace *self, IdeContext *context) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_assert (IDE_IS_MAIN_THREAD ()); g_assert (IDE_IS_WORKSPACE (self)); g_assert (IDE_IS_CONTEXT (context)); priv->addins = ide_extension_set_adapter_new (NULL, NULL, IDE_TYPE_WORKSPACE_ADDIN, "Workspace-Kind", IDE_WORKSPACE_GET_CLASS (self)->kind); g_signal_connect (priv->addins, "extension-added", G_CALLBACK (ide_workspace_addin_added_cb), self); g_signal_connect (priv->addins, "extension-removed", G_CALLBACK (ide_workspace_addin_removed_cb), self); ide_extension_set_adapter_foreach (priv->addins, ide_workspace_addin_added_cb, self); } static void ide_workspace_addin_surface_set_cb (IdeExtensionSetAdapter *set, PeasPluginInfo *plugin_info, PeasExtension *exten, gpointer user_data) { IdeWorkspaceAddin *addin = (IdeWorkspaceAddin *)exten; IdeSurface *surface = user_data; g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set)); g_assert (plugin_info != NULL); g_assert (IDE_IS_WORKSPACE_ADDIN (addin)); g_assert (!surface || IDE_IS_SURFACE (surface)); ide_workspace_addin_surface_set (addin, surface); } static void ide_workspace_real_surface_set (IdeWorkspace *self, IdeSurface *surface) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_assert (IDE_IS_MAIN_THREAD ()); g_assert (IDE_IS_WORKSPACE (self)); g_assert (!surface || IDE_IS_SURFACE (surface)); if (priv->addins != NULL) ide_extension_set_adapter_foreach (priv->addins, ide_workspace_addin_surface_set_cb, surface); } /** * ide_workspace_foreach_surface: * @self: a #IdeWorkspace * @callback: (scope call): a #GtkCallback to execute for every surface * @user_data: user data for @callback * * Calls callback for every #IdeSurface based #GtkWidget that is registered * in the workspace. * * Since: 3.32 */ void ide_workspace_foreach_surface (IdeWorkspace *self, GtkCallback callback, gpointer user_data) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_assert (IDE_IS_WORKSPACE (self)); g_assert (callback != NULL); gtk_container_foreach (GTK_CONTAINER (priv->surfaces), callback, user_data); } static void ide_workspace_agree_to_shutdown_cb (GtkWidget *widget, gpointer user_data) { gboolean *blocked = user_data; g_assert (IDE_IS_MAIN_THREAD ()); g_assert (IDE_IS_SURFACE (widget)); g_assert (blocked != NULL); *blocked |= !ide_surface_agree_to_shutdown (IDE_SURFACE (widget)); } static void ide_workspace_addin_can_close_cb (IdeExtensionSetAdapter *adapter, PeasPluginInfo *plugin_info, PeasExtension *exten, gpointer user_data) { IdeWorkspaceAddin *addin = (IdeWorkspaceAddin *)exten; gboolean *blocked = user_data; g_assert (IDE_IS_MAIN_THREAD ()); g_assert (IDE_IS_WORKSPACE_ADDIN (addin)); g_assert (blocked != NULL); *blocked |= !ide_workspace_addin_can_close (addin); } static gboolean ide_workspace_agree_to_shutdown (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); gboolean blocked = FALSE; g_assert (IDE_IS_MAIN_THREAD ()); g_assert (IDE_IS_WORKSPACE (self)); ide_workspace_foreach_surface (self, ide_workspace_agree_to_shutdown_cb, &blocked); if (!blocked) ide_extension_set_adapter_foreach (priv->addins, ide_workspace_addin_can_close_cb, &blocked); return !blocked; } static gboolean ide_workspace_delete_event (GtkWidget *widget, GdkEventAny *any) { IdeWorkspace *self = (IdeWorkspace *)widget; IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); IdeWorkbench *workbench; g_assert (IDE_IS_WORKSPACE (self)); g_assert (any != NULL); /* TODO: * * If there are any active transfers, we want to ask the user if they * are sure they want to exit and risk losing them. We can allow them * to be completed in the background. * * Note that we only want to do this on the final workspace window. */ if (!ide_workspace_agree_to_shutdown (self)) return GDK_EVENT_STOP; g_cancellable_cancel (priv->cancellable); workbench = ide_widget_get_workbench (widget); if (ide_workbench_has_project (workbench) && _ide_workbench_is_last_workspace (workbench, self)) { gtk_widget_hide (GTK_WIDGET (self)); ide_workbench_unload_async (workbench, NULL, NULL, NULL); return GDK_EVENT_STOP; } return GDK_EVENT_PROPAGATE; } static void ide_workspace_notify_surface_cb (IdeWorkspace *self, GParamSpec *pspec, GtkStack *surfaces) { GtkWidget *visible_child; IdeHeaderBar *header_bar; g_assert (IDE_IS_WORKSPACE (self)); g_assert (GTK_IS_STACK (surfaces)); visible_child = gtk_stack_get_visible_child (surfaces); if (!IDE_IS_SURFACE (visible_child)) visible_child = NULL; if (visible_child != NULL) gtk_widget_grab_focus (visible_child); if ((header_bar = ide_workspace_get_header_bar (self))) { if (visible_child != NULL) dzl_gtk_widget_mux_action_groups (GTK_WIDGET (header_bar), visible_child, MUX_ACTIONS_KEY); else dzl_gtk_widget_mux_action_groups (GTK_WIDGET (header_bar), NULL, MUX_ACTIONS_KEY); } g_signal_emit (self, signals [SURFACE_SET], 0, visible_child); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VISIBLE_SURFACE]); } static void ide_workspace_destroy (GtkWidget *widget) { IdeWorkspace *self = (IdeWorkspace *)widget; IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); GtkWindowGroup *group; g_assert (IDE_IS_WORKSPACE (self)); ide_clear_and_destroy_object (&priv->addins); group = gtk_window_get_group (GTK_WINDOW (self)); if (IDE_IS_WORKBENCH (group)) ide_workbench_remove_workspace (IDE_WORKBENCH (group), self); GTK_WIDGET_CLASS (ide_workspace_parent_class)->destroy (widget); } /** * ide_workspace_class_set_kind: * @klass: a #IdeWorkspaceClass * * Sets the shorthand name for the kind of workspace. This is used to limit * what #IdeWorkspaceAddin may load within the workspace. * * Since: 3.32 */ void ide_workspace_class_set_kind (IdeWorkspaceClass *klass, const gchar *kind) { g_return_if_fail (IDE_IS_WORKSPACE_CLASS (klass)); klass->kind = g_intern_string (kind); } static void ide_workspace_foreach_page_cb (GtkWidget *widget, gpointer user_data) { ForeachPage *state = user_data; if (IDE_IS_SURFACE (widget)) ide_surface_foreach_page (IDE_SURFACE (widget), state->callback, state->user_data); } static void ide_workspace_real_foreach_page (IdeWorkspace *self, GtkCallback callback, gpointer user_data) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); ForeachPage state = { callback, user_data }; g_assert (IDE_IS_WORKSPACE (self)); g_assert (callback != NULL); gtk_container_foreach (GTK_CONTAINER (priv->surfaces), ide_workspace_foreach_page_cb, &state); } #if 0 static void ide_workspace_set_surface_fullscreen_cb (GtkWidget *widget, gpointer user_data) { g_assert (GTK_IS_WIDGET (widget)); if (IDE_IS_SURFACE (widget)) _ide_surface_set_fullscreen (IDE_SURFACE (widget), !!user_data); } static void ide_workspace_real_set_fullscreen (DzlApplicationWindow *window, gboolean fullscreen) { IdeWorkspace *self = (IdeWorkspace *)window; IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); GtkWidget *titlebar; g_assert (IDE_IS_WORKSPACE (self)); DZL_APPLICATION_WINDOW_CLASS (ide_workspace_parent_class)->set_fullscreen (window, fullscreen); titlebar = dzl_application_window_get_titlebar (window); gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (titlebar), !fullscreen); gtk_container_foreach (GTK_CONTAINER (priv->surfaces), ide_workspace_set_surface_fullscreen_cb, GUINT_TO_POINTER (fullscreen)); } #endif static void ide_workspace_grab_focus (GtkWidget *widget) { IdeWorkspace *self = (IdeWorkspace *)widget; IdeSurface *surface; g_assert (IDE_IS_WORKSPACE (self)); if ((surface = ide_workspace_get_visible_surface (self))) gtk_widget_grab_focus (GTK_WIDGET (surface)); } static gboolean ide_workspace_key_press_event (GtkWidget *widget, GdkEventKey *event) { IdeWorkspace *self = (IdeWorkspace *)widget; IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); gboolean ret; g_assert (IDE_IS_WORKSPACE (self)); g_assert (event != NULL); /* Be re-entrant safe from the shortcut manager */ if (priv->in_key_press) return GTK_WIDGET_CLASS (ide_workspace_parent_class)->key_press_event (widget, event); priv->in_key_press = TRUE; ret = dzl_shortcut_manager_handle_event (NULL, event, widget); priv->in_key_press = FALSE; return ret; } static void ide_workspace_finalize (GObject *object) { IdeWorkspace *self = (IdeWorkspace *)object; IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_clear_object (&priv->context); g_clear_object (&priv->cancellable); G_OBJECT_CLASS (ide_workspace_parent_class)->finalize (object); } static void ide_workspace_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { IdeWorkspace *self = IDE_WORKSPACE (object); switch (prop_id) { case PROP_CONTEXT: g_value_set_object (value, ide_workspace_get_context (self)); break; case PROP_VISIBLE_SURFACE: g_value_set_object (value, ide_workspace_get_visible_surface (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_workspace_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { IdeWorkspace *self = IDE_WORKSPACE (object); switch (prop_id) { case PROP_VISIBLE_SURFACE: ide_workspace_set_visible_surface (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_workspace_class_init (IdeWorkspaceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); //DzlApplicationWindowClass *window_class = DZL_APPLICATION_WINDOW_CLASS (klass); object_class->finalize = ide_workspace_finalize; object_class->get_property = ide_workspace_get_property; object_class->set_property = ide_workspace_set_property; widget_class->destroy = ide_workspace_destroy; widget_class->delete_event = ide_workspace_delete_event; widget_class->grab_focus = ide_workspace_grab_focus; widget_class->key_press_event = ide_workspace_key_press_event; //window_class->set_fullscreen = ide_workspace_real_set_fullscreen; klass->foreach_page = ide_workspace_real_foreach_page; klass->context_set = ide_workspace_real_context_set; klass->surface_set = ide_workspace_real_surface_set; /** * IdeWorkspace:context: * * The "context" property is the #IdeContext for the workspace. This is set * when the workspace joins a workbench. * * Since: 3.32 */ properties [PROP_CONTEXT] = g_param_spec_object ("context", "Context", "The IdeContext for the workspace, inherited from workbench", IDE_TYPE_CONTEXT, (G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeWorkspace:visible-surface: * * The "visible-surface" property contains the currently foremost surface * in the workspaces stack of surfaces. Usually, this is the editor surface, * but may be other surfaces such as build preferences, profiler, etc. * * Since: 3.32 */ properties [PROP_VISIBLE_SURFACE] = g_param_spec_object ("visible-surface", "Visible Surface", "The currently visible surface", IDE_TYPE_SURFACE, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); /** * IdeWorkspace::surface-set: * @self: an #IdeWorkspace * @surface: (nullable): an #IdeSurface * * The "surface-set" signal is emitted when the current surface changes * within the workspace. * * Since: 3.32 */ signals [SURFACE_SET] = g_signal_new ("surface-set", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (IdeWorkspaceClass, surface_set), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, IDE_TYPE_SURFACE); g_signal_set_va_marshaller (signals [SURFACE_SET], G_TYPE_FROM_CLASS (klass), g_cclosure_marshal_VOID__OBJECTv); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/libide-gui/ui/ide-workspace.ui"); gtk_widget_class_bind_template_child_private (widget_class, IdeWorkspace, event_box); gtk_widget_class_bind_template_child_private (widget_class, IdeWorkspace, overlay); gtk_widget_class_bind_template_child_private (widget_class, IdeWorkspace, surfaces); gtk_widget_class_bind_template_child_private (widget_class, IdeWorkspace, vbox); } static void ide_workspace_init (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_autofree gchar *app_id = NULL; priv->mru_link.data = self; gtk_widget_init_template (GTK_WIDGET (self)); g_signal_connect_object (priv->surfaces, "notify::visible-child", G_CALLBACK (ide_workspace_notify_surface_cb), self, G_CONNECT_SWAPPED); /* Add org-gnome-Builder style CSS identifier */ app_id = g_strdelimit (g_strdup (ide_get_application_id ()), ".", '-'); dzl_gtk_widget_add_style_class (GTK_WIDGET (self), app_id); dzl_gtk_widget_add_style_class (GTK_WIDGET (self), "workspace"); /* Add events for motion controller of fullscreen titlebar */ gtk_widget_add_events (GTK_WIDGET (priv->event_box), (GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK)); /* Initialize GActions for workspace */ _ide_workspace_init_actions (self); } GList * _ide_workspace_get_mru_link (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_assert (IDE_IS_WORKSPACE (self)); return &priv->mru_link; } /** * ide_workspace_get_context: * * Gets the #IdeContext for the #IdeWorkspace, which is set when the * workspace joins an #IdeWorkbench. * * Returns: (transfer none) (nullable): an #IdeContext or %NULL * * Since: 3.32 */ IdeContext * ide_workspace_get_context (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_val_if_fail (IDE_IS_WORKSPACE (self), NULL); return priv->context; } void _ide_workspace_set_context (IdeWorkspace *self, IdeContext *context) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_if_fail (IDE_IS_WORKSPACE (self)); g_return_if_fail (IDE_IS_CONTEXT (context)); g_return_if_fail (priv->context == NULL); if (g_set_object (&priv->context, context)) { if (IDE_WORKSPACE_GET_CLASS (self)->context_set) IDE_WORKSPACE_GET_CLASS (self)->context_set (self, context); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONTEXT]); } } /** * ide_workspace_get_cancellable: * @self: a #IdeWorkspace * * Gets a cancellable for a window. This is useful when you want operations * to be cancelled if a window is closed. * * Returns: (transfer none): a #GCancellable * * Since: 3.32 */ GCancellable * ide_workspace_get_cancellable (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL); g_return_val_if_fail (IDE_IS_WORKSPACE (self), NULL); if (priv->cancellable == NULL) priv->cancellable = g_cancellable_new (); return priv->cancellable; } /** * ide_workspace_foreach_page: * @self: a #IdeWorkspace * @callback: (scope call): a callback to execute for each view * @user_data: closure data for @callback * * Calls @callback for each #IdePage found within the workspace. * * Since: 3.32 */ void ide_workspace_foreach_page (IdeWorkspace *self, GtkCallback callback, gpointer user_data) { g_return_if_fail (IDE_IS_WORKSPACE (self)); g_return_if_fail (callback != NULL); if (IDE_WORKSPACE_GET_CLASS (self)->foreach_page) IDE_WORKSPACE_GET_CLASS (self)->foreach_page (self, callback, user_data); } /** * ide_workspace_get_header_bar: * @self: a #IdeWorkspace * * Gets the headerbar for the workspace, if it is an #IdeHeaderBar. * Also works around Gtk giving back a GtkStack for the header bar. * * Returns: (nullable) (transfer none): an #IdeHeaderBar or %NULL * * Since: 3.32 */ IdeHeaderBar * ide_workspace_get_header_bar (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); IdeHeaderBar *ret = NULL; GList *children; g_return_val_if_fail (IDE_IS_WORKSPACE (self), NULL); children = gtk_container_get_children (GTK_CONTAINER (priv->vbox)); for (const GList *iter = children; iter; iter = iter->next) { GtkWidget *widget = iter->data; if (GTK_IS_STACK (widget)) widget = gtk_stack_get_visible_child (GTK_STACK (widget)); if (IDE_IS_HEADER_BAR (widget)) { ret = IDE_HEADER_BAR (widget); break; } } g_list_free (children); return ret; } /** * ide_workspace_add_surface: * @self: a #IdeWorkspace * * Adds a new #IdeSurface to the workspace. * * Since: 3.32 */ void ide_workspace_add_surface (IdeWorkspace *self, IdeSurface *surface) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_autofree gchar *title = NULL; g_return_if_fail (IDE_IS_WORKSPACE (self)); g_return_if_fail (IDE_IS_SURFACE (surface)); if (DZL_IS_DOCK_ITEM (surface)) title = dzl_dock_item_get_title (DZL_DOCK_ITEM (surface)); gtk_container_add_with_properties (GTK_CONTAINER (priv->surfaces), GTK_WIDGET (surface), "name", gtk_widget_get_name (GTK_WIDGET (surface)), "title", title, NULL); } /** * ide_workspace_set_visible_surface_name: * @self: a #IdeWorkspace * @visible_surface_name: the name of the #IdeSurface * * Sets the visible surface based on the name of the surface. The name of the * surface comes from gtk_widget_get_name(), which should be set when creating * the surface using gtk_widget_set_name(). * * Since: 3.32 */ void ide_workspace_set_visible_surface_name (IdeWorkspace *self, const gchar *visible_surface_name) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_if_fail (IDE_IS_WORKSPACE (self)); g_return_if_fail (visible_surface_name != NULL); gtk_stack_set_visible_child_name (priv->surfaces, visible_surface_name); } /** * ide_workspace_get_visible_surface: * @self: a #IdeWorkspace * * Gets the currently visible #IdeSurface, or %NULL * * Returns: (transfer none) (nullable): an #IdeSurface or %NULL * * Since: 3.32 */ IdeSurface * ide_workspace_get_visible_surface (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); GtkWidget *child; g_return_val_if_fail (IDE_IS_WORKSPACE (self), NULL); child = gtk_stack_get_visible_child (priv->surfaces); if (!IDE_IS_SURFACE (child)) child = NULL; return IDE_SURFACE (child); } /** * ide_workspace_set_visible_surface: * @self: a #IdeWorkspace * @surface: an #IdeSurface * * Sets the #IdeWorkspace:visible-surface property which is the currently * visible #IdeSurface in the workspace. * * Since: 3.32 */ void ide_workspace_set_visible_surface (IdeWorkspace *self, IdeSurface *surface) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_if_fail (IDE_IS_WORKSPACE (self)); g_return_if_fail (IDE_IS_SURFACE (surface)); gtk_stack_set_visible_child (priv->surfaces, GTK_WIDGET (surface)); } /** * ide_workspace_get_surface_by_name: * @self: a #IdeWorkspace * @name: the name of the surface * * Locates an #IdeSurface that has been added to the workspace by the name * that was registered for the widget using gtk_widget_set_name(). * * Returns: (transfer none) (nullable): an #IdeSurface or %NULL * * Since: 3.32 */ IdeSurface * ide_workspace_get_surface_by_name (IdeWorkspace *self, const gchar *name) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); GtkWidget *child; g_return_val_if_fail (IDE_IS_WORKSPACE (self), NULL); g_return_val_if_fail (name != NULL, NULL); child = gtk_stack_get_child_by_name (priv->surfaces, name); return IDE_IS_SURFACE (child) ? IDE_SURFACE (child) : NULL; } static GObject * ide_workspace_get_internal_child (GtkBuildable *buildable, GtkBuilder *builder, const gchar *child_name) { IdeWorkspace *self = (IdeWorkspace *)buildable; IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_assert (GTK_IS_BUILDABLE (buildable)); g_assert (GTK_IS_BUILDER (builder)); g_assert (child_name != NULL); if (ide_str_equal0 (child_name, "surfaces")) return G_OBJECT (priv->surfaces); return NULL; } static void ide_workspace_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *object, const char *type) { IdeWorkspace *self = (IdeWorkspace *)buildable; IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_assert (IDE_IS_WORKSPACE (self)); g_assert (GTK_IS_BUILDER (builder)); if (g_strcmp0 (type, "titlebar") == 0 && GTK_IS_WIDGET (object)) gtk_box_pack_start (priv->vbox, GTK_WIDGET (object), FALSE, FALSE, 0); else parent_builder->add_child (buildable, builder, object, type); } static void buildable_iface_init (GtkBuildableIface *iface) { parent_builder = g_type_interface_peek_parent (iface); iface->get_internal_child = ide_workspace_get_internal_child; iface->add_child = ide_workspace_add_child; } /** * ide_workspace_get_overlay: * @self: a #IdeWorkspace * * Gets a #GtkOverlay that contains all of the primary contents of the window * (everything except the headerbar). This can be used by plugins to draw * above the workspace contents. * * Returns: (transfer none): a #GtkOverlay * * Since: 3.32 */ GtkOverlay * ide_workspace_get_overlay (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_val_if_fail (IDE_IS_WORKSPACE (self), NULL); return priv->overlay; } /** * ide_workspace_get_most_recent_page: * @self: a #IdeWorkspace * * Gets the most recently focused #IdePage. * * Returns: (transfer none) (nullable): an #IdePage or %NULL * * Since: 3.32 */ IdePage * ide_workspace_get_most_recent_page (IdeWorkspace *self) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_val_if_fail (IDE_IS_WORKSPACE (self), NULL); if (priv->page_mru.head != NULL) return IDE_PAGE (priv->page_mru.head->data); return NULL; } void _ide_workspace_add_page_mru (IdeWorkspace *self, GList *mru_link) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_if_fail (IDE_IS_WORKSPACE (self)); g_return_if_fail (mru_link != NULL); g_return_if_fail (mru_link->prev == NULL); g_return_if_fail (mru_link->next == NULL); g_return_if_fail (IDE_IS_PAGE (mru_link->data)); g_debug ("Adding %s to page MRU", G_OBJECT_TYPE_NAME (mru_link->data)); g_queue_push_head_link (&priv->page_mru, mru_link); } void _ide_workspace_remove_page_mru (IdeWorkspace *self, GList *mru_link) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_if_fail (IDE_IS_WORKSPACE (self)); g_return_if_fail (mru_link != NULL); g_return_if_fail (IDE_IS_PAGE (mru_link->data)); g_debug ("Removing %s from page MRU", G_OBJECT_TYPE_NAME (mru_link->data)); g_queue_unlink (&priv->page_mru, mru_link); } void _ide_workspace_move_front_page_mru (IdeWorkspace *self, GList *mru_link) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (self); g_return_if_fail (IDE_IS_WORKSPACE (self)); g_return_if_fail (mru_link != NULL); g_return_if_fail (IDE_IS_PAGE (mru_link->data)); if (mru_link == priv->page_mru.head) return; g_debug ("Moving %s to front of page MRU", G_OBJECT_TYPE_NAME (mru_link->data)); g_queue_unlink (&priv->page_mru, mru_link); g_queue_push_head_link (&priv->page_mru, mru_link); } /** * ide_workspace_addin_find_by_module_name: * @workspace: an #IdeWorkspace * @module_name: the name of the addin module * * Finds the addin (if any) matching the plugin's @module_name. * * Returns: (transfer none) (nullable): an #IdeWorkspaceAddin or %NULL * * Since: 3.40 */ IdeWorkspaceAddin * ide_workspace_addin_find_by_module_name (IdeWorkspace *workspace, const gchar *module_name) { IdeWorkspacePrivate *priv = ide_workspace_get_instance_private (workspace); PeasPluginInfo *plugin_info; PeasExtension *ret = NULL; PeasEngine *engine; g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL); g_return_val_if_fail (IDE_IS_WORKSPACE (workspace), NULL); g_return_val_if_fail (module_name != NULL, NULL); if (priv->addins == NULL) return NULL; engine = peas_engine_get_default (); if ((plugin_info = peas_engine_get_plugin_info (engine, module_name))) ret = ide_extension_set_adapter_get_extension (priv->addins, plugin_info); return IDE_WORKSPACE_ADDIN (ret); }