gem-graph-client/libide/gui/ide-workspace.c

1092 lines
32 KiB
C
Raw Normal View History

/* ide-workspace.c
*
* Copyright 2014-2019 Christian Hergert <chergert@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-workspace"
#include "config.h"
#include <libide-plugins.h>
#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);
}