From ba429d344132c088177e853cce8ff7181570b221 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 19:42:51 +0200 Subject: Adding upstream version 44.2. Signed-off-by: Daniel Baumann --- gedit/gedit-notebook.c | 672 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 672 insertions(+) create mode 100644 gedit/gedit-notebook.c (limited to 'gedit/gedit-notebook.c') diff --git a/gedit/gedit-notebook.c b/gedit/gedit-notebook.c new file mode 100644 index 0000000..d06dd49 --- /dev/null +++ b/gedit/gedit-notebook.c @@ -0,0 +1,672 @@ +/* + * gedit-notebook.c + * This file is part of gedit + * + * Copyright (C) 2005 - Paolo Maggi + * Copyright (C) 2015 - Sébastien Wilmet + * + * 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 2 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 . + */ + +/* This file is a modified version of the epiphany file ephy-notebook.c + * Here the relevant copyright: + * + * Copyright (C) 2002 Christophe Fergeau + * Copyright (C) 2003 Marco Pesenti Gritti + * Copyright (C) 2003, 2004 Christian Persch + */ + +#include "gedit-notebook.h" +#include "gedit-tab-label.h" + +#define GEDIT_NOTEBOOK_GROUP_NAME "GeditNotebookGroup" + +/* The DND targets defined in GeditView start at 100. + * Those defined in GtkSourceView start at 200. + */ +#define TARGET_TAB 150 + +struct _GeditNotebookPrivate +{ + /* History of focused pages. The first element contains the most recent + * one. + */ + GList *focused_pages; + + guint ignore_focused_page_update : 1; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GeditNotebook, gedit_notebook, GTK_TYPE_NOTEBOOK) + +enum +{ + TAB_CLOSE_REQUEST, + SHOW_POPUP_MENU, + CHANGE_TO_PAGE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +gedit_notebook_finalize (GObject *object) +{ + GeditNotebook *notebook = GEDIT_NOTEBOOK (object); + + g_list_free (notebook->priv->focused_pages); + + G_OBJECT_CLASS (gedit_notebook_parent_class)->finalize (object); +} + +static void +gedit_notebook_grab_focus (GtkWidget *widget) +{ + GtkNotebook *notebook = GTK_NOTEBOOK (widget); + gint current_page; + GtkWidget *tab; + + current_page = gtk_notebook_get_current_page (notebook); + tab = gtk_notebook_get_nth_page (notebook, current_page); + + if (tab != NULL) + { + gtk_widget_grab_focus (tab); + } +} + +static gint +find_tab_num_at_pos (GtkNotebook *notebook, + gint screen_x, + gint screen_y) +{ + GtkPositionType tab_pos; + GtkAllocation tab_allocation; + gint page_num; + + tab_pos = gtk_notebook_get_tab_pos (notebook); + + for (page_num = 0; ; page_num++) + { + GtkWidget *page; + GtkWidget *tab_label; + gint max_x, max_y, x_root, y_root; + + page = gtk_notebook_get_nth_page (notebook, page_num); + + if (page == NULL) + { + break; + } + + tab_label = gtk_notebook_get_tab_label (notebook, page); + g_return_val_if_fail (tab_label != NULL, -1); + + if (!gtk_widget_get_mapped (tab_label)) + { + continue; + } + + gdk_window_get_origin (gtk_widget_get_window (tab_label), &x_root, &y_root); + + gtk_widget_get_allocation (tab_label, &tab_allocation); + max_x = x_root + tab_allocation.x + tab_allocation.width; + max_y = y_root + tab_allocation.y + tab_allocation.height; + + if ((tab_pos == GTK_POS_TOP || tab_pos == GTK_POS_BOTTOM) && + screen_x <= max_x) + { + return page_num; + } + + if ((tab_pos == GTK_POS_LEFT || tab_pos == GTK_POS_RIGHT) && + screen_y <= max_y) + { + return page_num; + } + } + + return -1; +} + +static gboolean +gedit_notebook_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + GtkNotebook *notebook = GTK_NOTEBOOK (widget); + + if (event->type == GDK_BUTTON_PRESS && + (event->state & gtk_accelerator_get_default_mod_mask ()) == 0) + { + gint tab_clicked; + + tab_clicked = find_tab_num_at_pos (notebook, event->x_root, event->y_root); + if (tab_clicked >= 0) + { + GtkWidget *tab; + + tab = gtk_notebook_get_nth_page (notebook, tab_clicked); + switch (event->button) + { + case GDK_BUTTON_SECONDARY: + g_signal_emit (G_OBJECT (widget), signals[SHOW_POPUP_MENU], 0, event, tab); + return GDK_EVENT_STOP; + + case GDK_BUTTON_MIDDLE: + g_signal_emit (G_OBJECT (notebook), signals[TAB_CLOSE_REQUEST], 0, tab); + return GDK_EVENT_STOP; + + default: + break; + } + } + } + + return GTK_WIDGET_CLASS (gedit_notebook_parent_class)->button_press_event (widget, event); +} + +/* + * We need to override this because when we don't show the tabs, like in + * fullscreen we need to have wrap around too + */ +static gboolean +gedit_notebook_change_current_page (GtkNotebook *notebook, + gint offset) +{ + gint current; + + current = gtk_notebook_get_current_page (notebook); + + if (current != -1) + { + gint target; + gboolean wrap_around; + + target = current + offset; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)), + "gtk-keynav-wrap-around", &wrap_around, + NULL); + + if (wrap_around) + { + if (target < 0) + { + target = gtk_notebook_get_n_pages (notebook) - 1; + } + else if (target >= gtk_notebook_get_n_pages (notebook)) + { + target = 0; + } + } + + gtk_notebook_set_current_page (notebook, target); + } + else + { + gtk_widget_error_bell (GTK_WIDGET (notebook)); + } + + return TRUE; +} + +static void +gedit_notebook_switch_page (GtkNotebook *notebook, + GtkWidget *page, + guint page_num) +{ + GeditNotebookPrivate *priv = GEDIT_NOTEBOOK (notebook)->priv; + + GTK_NOTEBOOK_CLASS (gedit_notebook_parent_class)->switch_page (notebook, page, page_num); + + if (!priv->ignore_focused_page_update) + { + gint page_num; + + /* Get again page_num and page, the signal handler may have + * changed them. + */ + page_num = gtk_notebook_get_current_page (notebook); + if (page_num != -1) + { + GtkWidget *page = gtk_notebook_get_nth_page (notebook, page_num); + g_assert (page != NULL); + + /* Remove the old page, we dont want to grow unnecessarily + * the list. + */ + priv->focused_pages = g_list_remove (priv->focused_pages, page); + + priv->focused_pages = g_list_prepend (priv->focused_pages, page); + } + } + + /* give focus to the tab */ + gtk_widget_grab_focus (page); +} + +static void +close_button_clicked_cb (GeditTabLabel *tab_label, + GeditNotebook *notebook) +{ + GeditTab *tab; + + tab = gedit_tab_label_get_tab (tab_label); + g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, tab); +} + +static void +switch_to_last_focused_page (GeditNotebook *notebook, + GeditTab *tab) +{ + if (notebook->priv->focused_pages != NULL) + { + GList *node; + GtkWidget *page; + gint page_num; + + node = notebook->priv->focused_pages; + page = GTK_WIDGET (node->data); + + page_num = gtk_notebook_page_num (GTK_NOTEBOOK (notebook), page); + g_return_if_fail (page_num != -1); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num); + } +} + +static void +drag_data_received_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint timestamp) +{ + GtkWidget *notebook; + GtkWidget *new_notebook; + GtkWidget *page; + + if (info != TARGET_TAB) + { + return; + } + + notebook = gtk_drag_get_source_widget (context); + + if (!GTK_IS_WIDGET (notebook)) + { + return; + } + + page = *(GtkWidget **) gtk_selection_data_get_data (selection_data); + g_return_if_fail (page != NULL); + + /* We need to iterate and get the notebook of the target view + * because we can have several notebooks per window. + */ + new_notebook = gtk_widget_get_ancestor (widget, GEDIT_TYPE_NOTEBOOK); + g_return_if_fail (new_notebook != NULL); + + if (notebook != new_notebook) + { + gedit_notebook_move_tab (GEDIT_NOTEBOOK (notebook), + GEDIT_NOTEBOOK (new_notebook), + GEDIT_TAB (page), + 0); + } + + gtk_drag_finish (context, TRUE, TRUE, timestamp); +} + +static void +gedit_notebook_page_removed (GtkNotebook *notebook, + GtkWidget *page, + guint page_num) +{ + GeditNotebookPrivate *priv = GEDIT_NOTEBOOK (notebook)->priv; + gboolean current_page; + + /* The page removed was the current page. */ + current_page = (priv->focused_pages != NULL && + priv->focused_pages->data == page); + + priv->focused_pages = g_list_remove (priv->focused_pages, page); + + if (current_page) + { + switch_to_last_focused_page (GEDIT_NOTEBOOK (notebook), + GEDIT_TAB (page)); + } +} + +static void +gedit_notebook_page_added (GtkNotebook *notebook, + GtkWidget *page, + guint page_num) +{ + GtkWidget *tab_label; + GeditView *view; + + g_return_if_fail (GEDIT_IS_TAB (page)); + + tab_label = gtk_notebook_get_tab_label (notebook, page); + g_return_if_fail (GEDIT_IS_TAB_LABEL (tab_label)); + + /* For a DND from one notebook to another, the same tab_label can be + * used, so we need to connect the signal here. + * More precisely, the same tab_label is used when the drop zone is in + * the tab labels (not the GeditView), that is, when the DND is handled + * by the GtkNotebook implementation. + */ + g_signal_connect (tab_label, + "close-clicked", + G_CALLBACK (close_button_clicked_cb), + notebook); + + view = gedit_tab_get_view (GEDIT_TAB (page)); + g_signal_connect (view, + "drag-data-received", + G_CALLBACK (drag_data_received_cb), + NULL); +} + +static void +gedit_notebook_remove (GtkContainer *container, + GtkWidget *widget) +{ + GtkNotebook *notebook = GTK_NOTEBOOK (container); + GeditNotebookPrivate *priv = GEDIT_NOTEBOOK (container)->priv; + GtkWidget *tab_label; + GeditView *view; + + g_return_if_fail (GEDIT_IS_TAB (widget)); + + tab_label = gtk_notebook_get_tab_label (notebook, widget); + g_return_if_fail (GEDIT_IS_TAB_LABEL (tab_label)); + + /* For a DND from one notebook to another, the same tab_label can be + * used, so we need to disconnect the signal. + */ + g_signal_handlers_disconnect_by_func (tab_label, + G_CALLBACK (close_button_clicked_cb), + notebook); + + view = gedit_tab_get_view (GEDIT_TAB (widget)); + g_signal_handlers_disconnect_by_func (view, drag_data_received_cb, NULL); + + /* This is where GtkNotebook will remove the page. By doing so, it + * will also switch to a new page, messing up our focus list. So we + * set a flag here to ignore the switch temporarily. + */ + priv->ignore_focused_page_update = TRUE; + + if (GTK_CONTAINER_CLASS (gedit_notebook_parent_class)->remove != NULL) + { + GTK_CONTAINER_CLASS (gedit_notebook_parent_class)->remove (container, + widget); + } + + priv->ignore_focused_page_update = FALSE; +} + +static gboolean +gedit_notebook_change_to_page (GeditNotebook *notebook, + gint page_num) +{ + gint n_pages; + + n_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); + + if (page_num >= n_pages) + { + return FALSE; + } + + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), + page_num); + + return TRUE; +} + +static void +gedit_notebook_class_init (GeditNotebookClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass); + GtkBindingSet *binding_set; + gint i; + + object_class->finalize = gedit_notebook_finalize; + + widget_class->grab_focus = gedit_notebook_grab_focus; + widget_class->button_press_event = gedit_notebook_button_press_event; + + container_class->remove = gedit_notebook_remove; + + notebook_class->change_current_page = gedit_notebook_change_current_page; + notebook_class->switch_page = gedit_notebook_switch_page; + notebook_class->page_removed = gedit_notebook_page_removed; + notebook_class->page_added = gedit_notebook_page_added; + + klass->change_to_page = gedit_notebook_change_to_page; + + signals[TAB_CLOSE_REQUEST] = + g_signal_new ("tab-close-request", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditNotebookClass, tab_close_request), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + + signals[SHOW_POPUP_MENU] = + g_signal_new ("show-popup-menu", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditNotebookClass, show_popup_menu), + NULL, NULL, NULL, + G_TYPE_NONE, + 2, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE, + GEDIT_TYPE_TAB); + + signals[CHANGE_TO_PAGE] = + g_signal_new ("change-to-page", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GeditNotebookClass, change_to_page), + NULL, NULL, NULL, + G_TYPE_BOOLEAN, 1, + G_TYPE_INT); + + binding_set = gtk_binding_set_by_class (klass); + for (i = 1; i < 10; i++) + { + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_0 + i, GDK_MOD1_MASK, + "change-to-page", 1, + G_TYPE_INT, i - 1); + } +} + +/** + * gedit_notebook_new: + * + * Creates a new #GeditNotebook object. + * + * Returns: a new #GeditNotebook + */ +GtkWidget * +gedit_notebook_new (void) +{ + return GTK_WIDGET (g_object_new (GEDIT_TYPE_NOTEBOOK, NULL)); +} + +static void +gedit_notebook_init (GeditNotebook *notebook) +{ + notebook->priv = gedit_notebook_get_instance_private (notebook); + + gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), TRUE); + gtk_notebook_set_group_name (GTK_NOTEBOOK (notebook), + GEDIT_NOTEBOOK_GROUP_NAME); + gtk_container_set_border_width (GTK_CONTAINER (notebook), 0); +} + +/** + * gedit_notebook_add_tab: + * @notebook: a #GeditNotebook + * @tab: a #GeditTab + * @position: the position where the @tab should be added + * @jump_to: %TRUE to set the @tab as active + * + * Adds the specified @tab to the @notebook. + */ +void +gedit_notebook_add_tab (GeditNotebook *notebook, + GeditTab *tab, + gint position, + gboolean jump_to) +{ + GtkWidget *tab_label; + GeditView *view; + GtkTargetList *target_list; + + g_return_if_fail (GEDIT_IS_NOTEBOOK (notebook)); + g_return_if_fail (GEDIT_IS_TAB (tab)); + + tab_label = gedit_tab_label_new (tab); + + gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), + GTK_WIDGET (tab), + tab_label, + position); + + gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (notebook), + GTK_WIDGET (tab), + TRUE); + + gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (notebook), + GTK_WIDGET (tab), + TRUE); + + gtk_container_child_set (GTK_CONTAINER (notebook), + GTK_WIDGET (tab), + "tab-expand", TRUE, + NULL); + + /* Drag and drop support: move a tab to another notebook, with the drop + * zone in the GeditView. The drop zone in the tab labels is already + * implemented by GtkNotebook. + */ + view = gedit_tab_get_view (tab); + target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (view)); + + if (target_list != NULL) + { + gtk_target_list_add (target_list, + gdk_atom_intern_static_string ("GTK_NOTEBOOK_TAB"), + GTK_TARGET_SAME_APP, + TARGET_TAB); + } + + /* The signal handler may have reordered the tabs */ + position = gtk_notebook_page_num (GTK_NOTEBOOK (notebook), + GTK_WIDGET (tab)); + + if (jump_to) + { + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), position); + gtk_widget_grab_focus (GTK_WIDGET (tab)); + } +} + +/** + * gedit_notebook_move_tab: + * @src: a #GeditNotebook + * @dest: a #GeditNotebook + * @tab: a #GeditTab + * @dest_position: the position for @tab + * + * Moves @tab from @src to @dest. + * If @dest_position is greater than or equal to the number of tabs + * of the destination nootebook or negative, tab will be moved to the + * end of the tabs. + */ +void +gedit_notebook_move_tab (GeditNotebook *src, + GeditNotebook *dest, + GeditTab *tab, + gint dest_position) +{ + g_return_if_fail (GEDIT_IS_NOTEBOOK (src)); + g_return_if_fail (GEDIT_IS_NOTEBOOK (dest)); + g_return_if_fail (src != dest); + g_return_if_fail (GEDIT_IS_TAB (tab)); + + /* Make sure the tab isn't destroyed while we move it. */ + g_object_ref (tab); + + /* Make sure the @src notebook isn't destroyed during the tab + * detachment, to prevent a crash in gtk_notebook_detach_tab(). In fact, + * if @tab is the last tab of @src, and if @src is not the last notebook + * of the GeditMultiNotebook, then @src will be destroyed when + * gtk_container_remove() is called by gtk_notebook_detach_tab(). + */ + g_object_ref (src); + gtk_notebook_detach_tab (GTK_NOTEBOOK (src), GTK_WIDGET (tab)); + g_object_unref (src); + + gedit_notebook_add_tab (dest, tab, dest_position, TRUE); + + g_object_unref (tab); +} + +/** + * gedit_notebook_remove_all_tabs: + * @notebook: a #GeditNotebook + * + * Removes all #GeditTab from @notebook. + */ +void +gedit_notebook_remove_all_tabs (GeditNotebook *notebook) +{ + GList *tabs; + GList *t; + + g_return_if_fail (GEDIT_IS_NOTEBOOK (notebook)); + + g_list_free (notebook->priv->focused_pages); + notebook->priv->focused_pages = NULL; + + /* Remove tabs in reverse order since it is faster + * due to how GtkNotebook works. + */ + tabs = gtk_container_get_children (GTK_CONTAINER (notebook)); + for (t = g_list_last (tabs); t != NULL; t = t->prev) + { + GtkWidget *tab = t->data; + gtk_container_remove (GTK_CONTAINER (notebook), tab); + } + + g_list_free (tabs); +} + +/* ex:set ts=8 noet: */ -- cgit v1.2.3