forked from AuroraMiddleware/gtk
textview: Redo context menus
Drop the ::populate-popup signal and implement the new context menu api.
This commit is contained in:
parent
964b2574cf
commit
037e205da0
@ -3098,6 +3098,9 @@ gtk_text_view_set_input_hints
|
||||
gtk_text_view_get_input_hints
|
||||
gtk_text_view_set_monospace
|
||||
gtk_text_view_get_monospace
|
||||
gtk_text_view_set_extra_menu
|
||||
gtk_text_view_get_extra_menu
|
||||
|
||||
GTK_TEXT_VIEW_PRIORITY_VALIDATE
|
||||
<SUBSECTION Standard>
|
||||
GTK_TEXT_VIEW
|
||||
|
@ -180,6 +180,8 @@ struct _GtkTextViewPrivate
|
||||
GtkAdjustment *hadjustment;
|
||||
GtkAdjustment *vadjustment;
|
||||
|
||||
GActionMap *context_actions;
|
||||
|
||||
/* X offset between widget coordinates and buffer coordinates
|
||||
* taking left_padding in account
|
||||
*/
|
||||
@ -219,6 +221,7 @@ struct _GtkTextViewPrivate
|
||||
|
||||
GtkIMContext *im_context;
|
||||
GtkWidget *popup_menu;
|
||||
GMenuModel *extra_menu;
|
||||
|
||||
GSList *children;
|
||||
|
||||
@ -273,7 +276,6 @@ struct _GtkTextViewPrivate
|
||||
guint vscroll_policy : 1;
|
||||
guint cursor_handle_dragged : 1;
|
||||
guint selection_handle_dragged : 1;
|
||||
guint populate_all : 1;
|
||||
};
|
||||
|
||||
struct _GtkTextPendingScroll
|
||||
@ -294,7 +296,6 @@ typedef enum
|
||||
|
||||
enum
|
||||
{
|
||||
POPULATE_POPUP,
|
||||
MOVE_CURSOR,
|
||||
PAGE_HORIZONTALLY,
|
||||
SET_ANCHOR,
|
||||
@ -340,8 +341,8 @@ enum
|
||||
PROP_VSCROLL_POLICY,
|
||||
PROP_INPUT_PURPOSE,
|
||||
PROP_INPUT_HINTS,
|
||||
PROP_POPULATE_ALL,
|
||||
PROP_MONOSPACE
|
||||
PROP_MONOSPACE,
|
||||
PROP_EXTRA_MENU
|
||||
};
|
||||
|
||||
static GQuark quark_text_selection_data = 0;
|
||||
@ -443,7 +444,6 @@ static void gtk_text_view_drag_data_received (GtkWidget *widget,
|
||||
GtkSelectionData *selection_data);
|
||||
|
||||
static gboolean gtk_text_view_popup_menu (GtkWidget *widget);
|
||||
|
||||
static void gtk_text_view_move_cursor (GtkTextView *text_view,
|
||||
GtkMovementStep step,
|
||||
gint count,
|
||||
@ -593,6 +593,10 @@ static void extend_selection (GtkTextView *text_view,
|
||||
GtkTextIter *start,
|
||||
GtkTextIter *end);
|
||||
|
||||
static void gtk_text_view_add_context_actions (GtkTextView *text_view);
|
||||
static void gtk_text_view_update_clipboard_actions (GtkTextView *text_view);
|
||||
static void gtk_text_view_update_emoji_action (GtkTextView *text_view);
|
||||
|
||||
|
||||
|
||||
/* FIXME probably need the focus methods. */
|
||||
@ -958,22 +962,9 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
|
||||
GTK_INPUT_HINT_NONE,
|
||||
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
|
||||
|
||||
/**
|
||||
* GtkTextView:populate-all:
|
||||
*
|
||||
* If :populate-all is %TRUE, the #GtkTextView::populate-popup
|
||||
* signal is also emitted for touch popups.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_POPULATE_ALL,
|
||||
g_param_spec_boolean ("populate-all",
|
||||
P_("Populate all"),
|
||||
P_("Whether to emit ::populate-popup for touch popups"),
|
||||
FALSE,
|
||||
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
|
||||
|
||||
/**
|
||||
* GtkTextview:monospace:
|
||||
* GtkTextView:monospace:
|
||||
*
|
||||
* If %TRUE, set the %GTK_STYLE_CLASS_MONOSPACE style class on the
|
||||
* text view to indicate that a monospace font is desired.
|
||||
@ -986,7 +977,13 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
|
||||
FALSE,
|
||||
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
|
||||
|
||||
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_EXTRA_MENU,
|
||||
g_param_spec_object ("extra-menu",
|
||||
P_("Extra menu"),
|
||||
P_("Menu model to append to the context menu"),
|
||||
G_TYPE_MENU_MODEL,
|
||||
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
|
||||
|
||||
/* GtkScrollable interface */
|
||||
g_object_class_override_property (gobject_class, PROP_HADJUSTMENT, "hadjustment");
|
||||
@ -1244,36 +1241,6 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
|
||||
NULL,
|
||||
G_TYPE_NONE, 0);
|
||||
|
||||
/**
|
||||
* GtkTextView::populate-popup:
|
||||
* @text_view: The text view on which the signal is emitted
|
||||
* @popup: the container that is being populated
|
||||
*
|
||||
* The ::populate-popup signal gets emitted before showing the
|
||||
* context menu of the text view.
|
||||
*
|
||||
* If you need to add items to the context menu, connect
|
||||
* to this signal and append your items to the @popup, which
|
||||
* will be a #GtkMenu in this case.
|
||||
*
|
||||
* If #GtkTextView:populate-all is %TRUE, this signal will
|
||||
* also be emitted to populate touch popups. In this case,
|
||||
* @popup will be a different container, e.g. a #GtkToolbar.
|
||||
*
|
||||
* The signal handler should not make assumptions about the
|
||||
* type of @widget, but check whether @popup is a #GtkMenu
|
||||
* or #GtkToolbar or another kind of container.
|
||||
*/
|
||||
signals[POPULATE_POPUP] =
|
||||
g_signal_new (I_("populate-popup"),
|
||||
G_OBJECT_CLASS_TYPE (gobject_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (GtkTextViewClass, populate_popup),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE, 1,
|
||||
GTK_TYPE_WIDGET);
|
||||
|
||||
/**
|
||||
* GtkTextView::select-all:
|
||||
* @text_view: the object which received the signal
|
||||
@ -1725,6 +1692,8 @@ gtk_text_view_init (GtkTextView *text_view)
|
||||
gtk_css_node_get_state (priv->text_window->css_node) & ~GTK_STATE_FLAG_DROP_ACTIVE);
|
||||
gtk_css_node_set_visible (priv->selection_node, FALSE);
|
||||
g_object_unref (priv->selection_node);
|
||||
|
||||
gtk_text_view_add_context_actions (text_view);
|
||||
}
|
||||
|
||||
GtkCssNode *
|
||||
@ -3645,6 +3614,10 @@ gtk_text_view_finalize (GObject *object)
|
||||
|
||||
g_free (priv->im_module);
|
||||
|
||||
g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
|
||||
g_clear_object (&priv->context_actions);
|
||||
g_clear_object (&priv->extra_menu);
|
||||
|
||||
G_OBJECT_CLASS (gtk_text_view_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
@ -3767,17 +3740,14 @@ gtk_text_view_set_property (GObject *object,
|
||||
gtk_text_view_set_input_hints (text_view, g_value_get_flags (value));
|
||||
break;
|
||||
|
||||
case PROP_POPULATE_ALL:
|
||||
if (text_view->priv->populate_all != g_value_get_boolean (value))
|
||||
{
|
||||
text_view->priv->populate_all = g_value_get_boolean (value);
|
||||
g_object_notify_by_pspec (object, pspec);
|
||||
}
|
||||
break;
|
||||
case PROP_MONOSPACE:
|
||||
gtk_text_view_set_monospace (text_view, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
case PROP_EXTRA_MENU:
|
||||
gtk_text_view_set_extra_menu (text_view, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -3890,14 +3860,14 @@ gtk_text_view_get_property (GObject *object,
|
||||
g_value_set_flags (value, gtk_text_view_get_input_hints (text_view));
|
||||
break;
|
||||
|
||||
case PROP_POPULATE_ALL:
|
||||
g_value_set_boolean (value, priv->populate_all);
|
||||
break;
|
||||
|
||||
case PROP_MONOSPACE:
|
||||
g_value_set_boolean (value, gtk_text_view_get_monospace (text_view));
|
||||
break;
|
||||
|
||||
case PROP_EXTRA_MENU:
|
||||
g_value_set_object (value, gtk_text_view_get_extra_menu (text_view));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -4191,6 +4161,9 @@ gtk_text_view_size_allocate (GtkWidget *widget,
|
||||
|
||||
if (priv->magnifier_popover)
|
||||
gtk_native_check_resize (GTK_NATIVE (priv->magnifier_popover));
|
||||
|
||||
if (priv->popup_menu)
|
||||
gtk_native_check_resize (GTK_NATIVE (priv->popup_menu));
|
||||
}
|
||||
|
||||
static void
|
||||
@ -4500,11 +4473,7 @@ gtk_text_view_unrealize (GtkWidget *widget)
|
||||
|
||||
gtk_text_view_remove_validate_idles (text_view);
|
||||
|
||||
if (priv->popup_menu)
|
||||
{
|
||||
gtk_widget_destroy (priv->popup_menu);
|
||||
priv->popup_menu = NULL;
|
||||
}
|
||||
g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
|
||||
|
||||
gtk_im_context_set_client_widget (priv->im_context, NULL);
|
||||
|
||||
@ -8458,35 +8427,40 @@ gtk_text_view_set_virtual_cursor_pos (GtkTextView *text_view,
|
||||
text_view->priv->virtual_cursor_y = (y == -1) ? pos.y + pos.height / 2 : y;
|
||||
}
|
||||
|
||||
/* Quick hack of a popup menu
|
||||
*/
|
||||
static void
|
||||
activate_cb (GtkWidget *menuitem,
|
||||
GtkTextView *text_view)
|
||||
hide_selection_bubble (GtkTextView *text_view)
|
||||
{
|
||||
const gchar *signal;
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
|
||||
signal = g_object_get_qdata (G_OBJECT (menuitem), quark_gtk_signal);
|
||||
g_signal_emit_by_name (text_view, signal);
|
||||
if (priv->selection_bubble && gtk_widget_get_visible (priv->selection_bubble))
|
||||
gtk_widget_hide (priv->selection_bubble);
|
||||
}
|
||||
|
||||
static void
|
||||
append_action_signal (GtkTextView *text_view,
|
||||
GtkWidget *menu,
|
||||
const gchar *label,
|
||||
const gchar *signal,
|
||||
gboolean sensitive)
|
||||
cut_clipboard_activated (GSimpleAction *action,
|
||||
GVariant *parameter,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkWidget *menuitem = gtk_menu_item_new_with_mnemonic (label);
|
||||
g_signal_emit_by_name (user_data, "cut-clipboard");
|
||||
hide_selection_bubble (GTK_TEXT_VIEW (user_data));
|
||||
}
|
||||
|
||||
g_object_set_qdata (G_OBJECT (menuitem), quark_gtk_signal, (char *)signal);
|
||||
g_signal_connect (menuitem, "activate",
|
||||
G_CALLBACK (activate_cb), text_view);
|
||||
static void
|
||||
copy_clipboard_activated (GSimpleAction *action,
|
||||
GVariant *parameter,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_signal_emit_by_name (user_data, "copy-clipboard");
|
||||
hide_selection_bubble (GTK_TEXT_VIEW (user_data));
|
||||
}
|
||||
|
||||
gtk_widget_set_sensitive (menuitem, sensitive);
|
||||
|
||||
gtk_widget_show (menuitem);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
|
||||
static void
|
||||
paste_clipboard_activated (GSimpleAction *action,
|
||||
GVariant *parameter,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_signal_emit_by_name (user_data, "paste-clipboard");
|
||||
hide_selection_bubble (GTK_TEXT_VIEW (user_data));
|
||||
}
|
||||
|
||||
static void
|
||||
@ -8512,24 +8486,32 @@ gtk_text_view_select_all (GtkWidget *widget,
|
||||
}
|
||||
|
||||
static void
|
||||
select_all_cb (GtkWidget *menuitem,
|
||||
GtkTextView *text_view)
|
||||
select_all_activated (GSimpleAction *action,
|
||||
GVariant *parameter,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkTextView *text_view = user_data;
|
||||
|
||||
gtk_text_view_select_all (GTK_WIDGET (text_view), TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
delete_cb (GtkTextView *text_view)
|
||||
delete_selection_activated (GSimpleAction *action,
|
||||
GVariant *parameter,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkTextView *text_view = user_data;
|
||||
|
||||
gtk_text_buffer_delete_selection (get_buffer (text_view), TRUE,
|
||||
text_view->priv->editable);
|
||||
}
|
||||
|
||||
static void
|
||||
popup_menu_detach (GtkWidget *attach_widget,
|
||||
GtkMenu *menu)
|
||||
insert_emoji_activated (GSimpleAction *action,
|
||||
GVariant *parameter,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTK_TEXT_VIEW (attach_widget)->priv->popup_menu = NULL;
|
||||
gtk_text_view_insert_emoji (GTK_TEXT_VIEW (user_data));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -8550,6 +8532,147 @@ range_contains_editable_text (const GtkTextIter *start,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_add_context_actions (GtkTextView *text_view)
|
||||
{
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
|
||||
GActionEntry entries[] = {
|
||||
{ "cut-clipboard", cut_clipboard_activated, NULL, NULL, NULL },
|
||||
{ "copy-clipboard", copy_clipboard_activated, NULL, NULL, NULL },
|
||||
{ "paste-clipboard", paste_clipboard_activated, NULL, NULL, NULL },
|
||||
{ "delete-selection", delete_selection_activated, NULL, NULL, NULL },
|
||||
{ "select-all", select_all_activated, NULL, NULL, NULL },
|
||||
{ "insert-emoji", insert_emoji_activated, NULL, NULL, NULL },
|
||||
};
|
||||
|
||||
GSimpleActionGroup *actions = g_simple_action_group_new ();
|
||||
GAction *action;
|
||||
|
||||
priv->context_actions = G_ACTION_MAP (actions);
|
||||
|
||||
g_action_map_add_action_entries (G_ACTION_MAP (actions), entries, G_N_ELEMENTS (entries), text_view);
|
||||
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (actions), "cut-clipboard");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (actions), "copy-clipboard");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (actions), "paste-clipboard");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (actions), "delete-selection");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (actions), "select-all");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (actions), "insert-emoji");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
|
||||
|
||||
gtk_widget_insert_action_group (GTK_WIDGET (text_view), "context", G_ACTION_GROUP (actions));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_update_clipboard_actions (GtkTextView *text_view)
|
||||
{
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
GdkClipboard *clipboard;
|
||||
gboolean have_selection;
|
||||
gboolean can_paste, can_insert;
|
||||
GAction *action;
|
||||
GtkTextIter iter, sel_start, sel_end;
|
||||
|
||||
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (text_view));
|
||||
can_paste = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (clipboard), G_TYPE_STRING);
|
||||
|
||||
have_selection = gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
|
||||
&sel_start, &sel_end);
|
||||
|
||||
gtk_text_buffer_get_iter_at_mark (get_buffer (text_view),
|
||||
&iter,
|
||||
gtk_text_buffer_get_insert (get_buffer (text_view)));
|
||||
|
||||
can_insert = gtk_text_iter_can_insert (&iter, priv->editable);
|
||||
|
||||
action = g_action_map_lookup_action (priv->context_actions, "cut-clipboard");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
|
||||
have_selection &&
|
||||
range_contains_editable_text (&sel_start, &sel_end, priv->editable));
|
||||
|
||||
action = g_action_map_lookup_action (priv->context_actions, "copy-clipboard");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), have_selection);
|
||||
|
||||
action = g_action_map_lookup_action (priv->context_actions, "paste-clipboard");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_insert && can_paste);
|
||||
|
||||
action = g_action_map_lookup_action (priv->context_actions, "delete-selection");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
|
||||
have_selection &&
|
||||
range_contains_editable_text (&sel_start, &sel_end, priv->editable));
|
||||
|
||||
action = g_action_map_lookup_action (priv->context_actions, "select-all");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
|
||||
gtk_text_buffer_get_char_count (priv->buffer) > 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_update_emoji_action (GtkTextView *text_view)
|
||||
{
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
GAction *action;
|
||||
|
||||
action = g_action_map_lookup_action (priv->context_actions, "insert-emoji");
|
||||
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
|
||||
(gtk_text_view_get_input_hints (text_view) & GTK_INPUT_HINT_NO_EMOJI) == 0);
|
||||
}
|
||||
|
||||
static GMenuModel *
|
||||
gtk_text_view_get_menu_model (GtkTextView *text_view)
|
||||
{
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
GMenu *menu, *section;
|
||||
GMenuItem *item;
|
||||
|
||||
menu = g_menu_new ();
|
||||
|
||||
section = g_menu_new ();
|
||||
item = g_menu_item_new (_("Cu_t"), "context.cut-clipboard");
|
||||
g_menu_item_set_attribute (item, "touch-icon", "s", "edit-cut-symbolic");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
item = g_menu_item_new (_("_Copy"), "context.copy-clipboard");
|
||||
g_menu_item_set_attribute (item, "touch-icon", "s", "edit-copy-symbolic");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
item = g_menu_item_new (_("_Paste"), "context.paste-clipboard");
|
||||
g_menu_item_set_attribute (item, "touch-icon", "s", "edit-paste-symbolic");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
item = g_menu_item_new (_("_Delete"), "context.delete-selection");
|
||||
g_menu_item_set_attribute (item, "touch-icon", "s", "edit-delete-symbolic");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
|
||||
g_object_unref (section);
|
||||
|
||||
section = g_menu_new ();
|
||||
|
||||
item = g_menu_item_new (_("Select _All"), "context.select-all");
|
||||
g_menu_item_set_attribute (item, "touch-icon", "s", "edit-select-all-symbolic");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
|
||||
item = g_menu_item_new ( _("Insert _Emoji"), "context.insert-emoji");
|
||||
g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
|
||||
g_menu_item_set_attribute (item, "touch-icon", "s", "face-smile-symbolic");
|
||||
g_menu_append_item (section, item);
|
||||
g_object_unref (item);
|
||||
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
|
||||
g_object_unref (section);
|
||||
|
||||
if (priv->extra_menu)
|
||||
g_menu_append_section (menu, NULL, priv->extra_menu);
|
||||
|
||||
return G_MENU_MODEL (menu);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_do_popup (GtkTextView *text_view,
|
||||
const GdkEvent *event)
|
||||
@ -8557,134 +8680,95 @@ gtk_text_view_do_popup (GtkTextView *text_view,
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
GdkEvent *trigger_event;
|
||||
|
||||
if (!gtk_widget_get_realized (GTK_WIDGET (text_view)))
|
||||
return;
|
||||
|
||||
if (event)
|
||||
trigger_event = gdk_event_copy (event);
|
||||
else
|
||||
trigger_event = gtk_get_current_event ();
|
||||
|
||||
if (gtk_widget_get_realized (GTK_WIDGET (text_view)))
|
||||
gtk_text_view_update_clipboard_actions (text_view);
|
||||
|
||||
if (!priv->popup_menu)
|
||||
{
|
||||
GMenuModel *model;
|
||||
|
||||
model = gtk_text_view_get_menu_model (text_view);
|
||||
priv->popup_menu = gtk_popover_menu_new_from_model (GTK_WIDGET (text_view), model);
|
||||
gtk_popover_set_position (GTK_POPOVER (priv->popup_menu), GTK_POS_BOTTOM);
|
||||
|
||||
gtk_popover_set_has_arrow (GTK_POPOVER (priv->popup_menu), FALSE);
|
||||
gtk_widget_set_halign (priv->popup_menu, GTK_ALIGN_START);
|
||||
|
||||
g_object_unref (model);
|
||||
}
|
||||
|
||||
if (trigger_event && gdk_event_triggers_context_menu (trigger_event))
|
||||
{
|
||||
GdkDevice *device;
|
||||
GdkRectangle rect = { 0, 0, 1, 1 };
|
||||
|
||||
device = gdk_event_get_device (trigger_event);
|
||||
|
||||
if (device && gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
|
||||
device = gdk_device_get_associated_device (device);
|
||||
|
||||
if (device)
|
||||
{
|
||||
GdkSurface *surface;
|
||||
double px, py;
|
||||
|
||||
surface = gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (text_view)));
|
||||
gdk_surface_get_device_position (surface, device, &px, &py, NULL);
|
||||
rect.x = round (px);
|
||||
rect.y = round (py);
|
||||
|
||||
gtk_widget_translate_coordinates (GTK_WIDGET (gtk_widget_get_native (GTK_WIDGET (text_view))),
|
||||
GTK_WIDGET (text_view),
|
||||
rect.x, rect.y,
|
||||
&rect.x, &rect.y);
|
||||
}
|
||||
|
||||
gtk_popover_set_pointing_to (GTK_POPOVER (priv->popup_menu), &rect);
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkWidget *menuitem;
|
||||
gboolean have_selection;
|
||||
gboolean can_insert, can_paste;
|
||||
GtkTextIter iter;
|
||||
GtkTextIter sel_start, sel_end;
|
||||
GdkRectangle iter_location;
|
||||
GdkRectangle visible_rect;
|
||||
gboolean is_visible;
|
||||
|
||||
if (priv->popup_menu)
|
||||
gtk_widget_destroy (priv->popup_menu);
|
||||
gtk_text_view_get_iter_location (text_view, &iter, &iter_location);
|
||||
gtk_text_view_get_visible_rect (text_view, &visible_rect);
|
||||
|
||||
priv->popup_menu = gtk_menu_new ();
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (priv->popup_menu),
|
||||
GTK_STYLE_CLASS_CONTEXT_MENU);
|
||||
is_visible = (iter_location.x + iter_location.width > visible_rect.x &&
|
||||
iter_location.x < visible_rect.x + visible_rect.width &&
|
||||
iter_location.y + iter_location.height > visible_rect.y &&
|
||||
iter_location.y < visible_rect.y + visible_rect.height);
|
||||
|
||||
gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
|
||||
GTK_WIDGET (text_view),
|
||||
popup_menu_detach);
|
||||
|
||||
have_selection = gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
|
||||
&sel_start, &sel_end);
|
||||
|
||||
gtk_text_buffer_get_iter_at_mark (get_buffer (text_view),
|
||||
&iter,
|
||||
gtk_text_buffer_get_insert (get_buffer (text_view)));
|
||||
|
||||
can_insert = gtk_text_iter_can_insert (&iter, priv->editable);
|
||||
can_paste = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (gtk_widget_get_clipboard (GTK_WIDGET (text_view))),
|
||||
GTK_TYPE_TEXT_BUFFER);
|
||||
|
||||
append_action_signal (text_view, priv->popup_menu, _("Cu_t"), "cut-clipboard",
|
||||
have_selection &&
|
||||
range_contains_editable_text (&sel_start, &sel_end,
|
||||
priv->editable));
|
||||
append_action_signal (text_view, priv->popup_menu, _("_Copy"), "copy-clipboard",
|
||||
have_selection);
|
||||
append_action_signal (text_view, priv->popup_menu, _("_Paste"), "paste-clipboard",
|
||||
can_insert && can_paste);
|
||||
|
||||
menuitem = gtk_menu_item_new_with_mnemonic (_("_Delete"));
|
||||
gtk_widget_set_sensitive (menuitem,
|
||||
have_selection &&
|
||||
range_contains_editable_text (&sel_start, &sel_end,
|
||||
priv->editable));
|
||||
g_signal_connect_swapped (menuitem, "activate",
|
||||
G_CALLBACK (delete_cb), text_view);
|
||||
gtk_widget_show (menuitem);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), menuitem);
|
||||
|
||||
menuitem = gtk_separator_menu_item_new ();
|
||||
gtk_widget_show (menuitem);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), menuitem);
|
||||
|
||||
menuitem = gtk_menu_item_new_with_mnemonic (_("Select _All"));
|
||||
gtk_widget_set_sensitive (menuitem,
|
||||
gtk_text_buffer_get_char_count (priv->buffer) > 0);
|
||||
g_signal_connect (menuitem, "activate",
|
||||
G_CALLBACK (select_all_cb), text_view);
|
||||
gtk_widget_show (menuitem);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), menuitem);
|
||||
|
||||
if ((gtk_text_view_get_input_hints (text_view) & GTK_INPUT_HINT_NO_EMOJI) == 0)
|
||||
if (is_visible)
|
||||
{
|
||||
menuitem = gtk_menu_item_new_with_mnemonic (_("Insert _Emoji"));
|
||||
gtk_widget_set_sensitive (menuitem, can_insert);
|
||||
g_signal_connect_swapped (menuitem, "activate",
|
||||
G_CALLBACK (gtk_text_view_insert_emoji), text_view);
|
||||
gtk_widget_show (menuitem);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), menuitem);
|
||||
}
|
||||
gtk_text_view_buffer_to_surface_coords (text_view,
|
||||
GTK_TEXT_WINDOW_WIDGET,
|
||||
iter_location.x,
|
||||
iter_location.y,
|
||||
&iter_location.x,
|
||||
&iter_location.y);
|
||||
|
||||
g_signal_emit (text_view, signals[POPULATE_POPUP],
|
||||
0, priv->popup_menu);
|
||||
|
||||
if (trigger_event && gdk_event_triggers_context_menu (trigger_event))
|
||||
gtk_menu_popup_at_pointer (GTK_MENU (priv->popup_menu), trigger_event);
|
||||
else
|
||||
{
|
||||
gtk_text_view_get_iter_location (text_view, &iter, &iter_location);
|
||||
gtk_text_view_get_visible_rect (text_view, &visible_rect);
|
||||
|
||||
is_visible = (iter_location.x + iter_location.width > visible_rect.x &&
|
||||
iter_location.x < visible_rect.x + visible_rect.width &&
|
||||
iter_location.y + iter_location.height > visible_rect.y &&
|
||||
iter_location.y < visible_rect.y + visible_rect.height);
|
||||
|
||||
if (is_visible)
|
||||
{
|
||||
gtk_text_view_buffer_to_surface_coords (text_view,
|
||||
GTK_TEXT_WINDOW_WIDGET,
|
||||
iter_location.x,
|
||||
iter_location.y,
|
||||
&iter_location.x,
|
||||
&iter_location.y);
|
||||
|
||||
gtk_menu_popup_at_rect (GTK_MENU (priv->popup_menu),
|
||||
gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (text_view))),
|
||||
&iter_location,
|
||||
GDK_GRAVITY_SOUTH_EAST,
|
||||
GDK_GRAVITY_NORTH_WEST,
|
||||
trigger_event);
|
||||
}
|
||||
else
|
||||
gtk_menu_popup_at_widget (GTK_MENU (priv->popup_menu),
|
||||
GTK_WIDGET (text_view),
|
||||
GDK_GRAVITY_CENTER,
|
||||
GDK_GRAVITY_CENTER,
|
||||
trigger_event);
|
||||
|
||||
gtk_menu_shell_select_first (GTK_MENU_SHELL (priv->popup_menu), FALSE);
|
||||
gtk_popover_set_pointing_to (GTK_POPOVER (priv->popup_menu), &iter_location);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_popover_popup (GTK_POPOVER (priv->popup_menu));
|
||||
|
||||
g_clear_object (&trigger_event);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_text_view_popup_menu (GtkWidget *widget)
|
||||
{
|
||||
gtk_text_view_do_popup (GTK_TEXT_VIEW (widget), NULL);
|
||||
gtk_text_view_do_popup (GTK_TEXT_VIEW (widget), NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -8741,32 +8825,58 @@ show_or_hide_handles (GtkWidget *popover,
|
||||
}
|
||||
|
||||
static void
|
||||
activate_bubble_cb (GtkWidget *item,
|
||||
GtkTextView *text_view)
|
||||
append_bubble_item (GtkTextView *text_view,
|
||||
GtkWidget *toolbar,
|
||||
GMenuModel *model,
|
||||
int index)
|
||||
{
|
||||
const gchar *signal;
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
GtkWidget *item, *image;
|
||||
GVariant *att;
|
||||
const char *icon_name;
|
||||
const char *action_name;
|
||||
GAction *action;
|
||||
GMenuModel *link;
|
||||
|
||||
signal = g_object_get_qdata (G_OBJECT (item), quark_gtk_signal);
|
||||
gtk_widget_hide (text_view->priv->selection_bubble);
|
||||
g_signal_emit_by_name (text_view, signal);
|
||||
}
|
||||
link = g_menu_model_get_item_link (model, index, "section");
|
||||
if (link)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < g_menu_model_get_n_items (link); i++)
|
||||
append_bubble_item (text_view, toolbar, link, i);
|
||||
g_object_unref (link);
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
append_bubble_action (GtkTextView *text_view,
|
||||
GtkWidget *toolbar,
|
||||
const gchar *label,
|
||||
const gchar *icon_name,
|
||||
const gchar *signal,
|
||||
gboolean sensitive)
|
||||
{
|
||||
GtkWidget *item;
|
||||
att = g_menu_model_get_item_attribute_value (model, index, "touch-icon", G_VARIANT_TYPE_STRING);
|
||||
if (att == NULL)
|
||||
return;
|
||||
|
||||
item = gtk_button_new_from_icon_name (icon_name);
|
||||
icon_name = g_variant_get_string (att, NULL);
|
||||
g_variant_unref (att);
|
||||
|
||||
att = g_menu_model_get_item_attribute_value (model, index, "action", G_VARIANT_TYPE_STRING);
|
||||
if (att == NULL)
|
||||
return;
|
||||
action_name = g_variant_get_string (att, NULL);
|
||||
g_variant_unref (att);
|
||||
|
||||
if (g_str_has_prefix (action_name, "context."))
|
||||
{
|
||||
action = g_action_map_lookup_action (priv->context_actions, action_name + strlen ("context."));
|
||||
if (action && !g_action_get_enabled (action))
|
||||
return;
|
||||
}
|
||||
|
||||
item = gtk_button_new ();
|
||||
gtk_widget_set_focus_on_click (item, FALSE);
|
||||
gtk_widget_set_tooltip_text (item, label);
|
||||
g_object_set_qdata (G_OBJECT (item), quark_gtk_signal, (char *)signal);
|
||||
g_signal_connect (item, "clicked", G_CALLBACK (activate_bubble_cb), text_view);
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (item), sensitive);
|
||||
image = gtk_image_new_from_icon_name (icon_name);
|
||||
gtk_widget_show (image);
|
||||
gtk_container_add (GTK_CONTAINER (item), image);
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (item), "image-button");
|
||||
gtk_actionable_set_action_name (GTK_ACTIONABLE (item), action_name);
|
||||
|
||||
gtk_widget_show (GTK_WIDGET (item));
|
||||
gtk_container_add (GTK_CONTAINER (toolbar), item);
|
||||
}
|
||||
|
||||
@ -8776,24 +8886,14 @@ gtk_text_view_selection_bubble_popup_show (gpointer user_data)
|
||||
GtkTextView *text_view = user_data;
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
cairo_rectangle_int_t rect;
|
||||
GdkClipboard *clipboard;
|
||||
gboolean has_selection;
|
||||
gboolean has_clipboard;
|
||||
gboolean can_insert;
|
||||
gboolean all_selected;
|
||||
GtkTextIter iter;
|
||||
GtkTextIter sel_start, sel_end;
|
||||
GtkTextIter start, end;
|
||||
GtkWidget *box;
|
||||
GtkWidget *toolbar;
|
||||
GMenuModel *model;
|
||||
int i;
|
||||
|
||||
gtk_text_view_update_clipboard_actions (text_view);
|
||||
|
||||
priv->selection_bubble_timeout_id = 0;
|
||||
has_selection = gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
|
||||
&sel_start, &sel_end);
|
||||
gtk_text_buffer_get_bounds (get_buffer (text_view), &start, &end);
|
||||
|
||||
all_selected = gtk_text_iter_equal (&start, &sel_start) &&
|
||||
gtk_text_iter_equal (&end, &sel_end);
|
||||
|
||||
g_clear_pointer (&priv->selection_bubble, gtk_widget_unparent);
|
||||
|
||||
@ -8813,25 +8913,12 @@ gtk_text_view_selection_bubble_popup_show (gpointer user_data)
|
||||
gtk_container_add (GTK_CONTAINER (priv->selection_bubble), box);
|
||||
gtk_container_add (GTK_CONTAINER (box), toolbar);
|
||||
|
||||
gtk_text_buffer_get_iter_at_mark (get_buffer (text_view), &iter,
|
||||
gtk_text_buffer_get_insert (get_buffer (text_view)));
|
||||
can_insert = gtk_text_iter_can_insert (&iter, priv->editable);
|
||||
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (text_view));
|
||||
has_clipboard = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (clipboard), GTK_TYPE_TEXT_BUFFER);
|
||||
model = gtk_text_view_get_menu_model (text_view);
|
||||
|
||||
append_bubble_action (text_view, toolbar, _("Select all"), "edit-select-all-symbolic", "select-all", !all_selected);
|
||||
for (i = 0; i < g_menu_model_get_n_items (model); i++)
|
||||
append_bubble_item (text_view, toolbar, model, i);
|
||||
|
||||
if (range_contains_editable_text (&sel_start, &sel_end, priv->editable) && has_selection)
|
||||
append_bubble_action (text_view, toolbar, _("Cut"), "edit-cut-symbolic", "cut-clipboard", TRUE);
|
||||
|
||||
if (has_selection)
|
||||
append_bubble_action (text_view, toolbar, _("Copy"), "edit-copy-symbolic", "copy-clipboard", TRUE);
|
||||
|
||||
if (can_insert)
|
||||
append_bubble_action (text_view, toolbar, _("Paste"), "edit-paste-symbolic", "paste-clipboard", has_clipboard);
|
||||
|
||||
if (priv->populate_all)
|
||||
g_signal_emit (text_view, signals[POPULATE_POPUP], 0, box);
|
||||
g_object_unref (model);
|
||||
|
||||
gtk_text_view_get_selection_rect (text_view, &rect);
|
||||
rect.x -= priv->xoffset;
|
||||
@ -9782,6 +9869,7 @@ gtk_text_view_set_input_hints (GtkTextView *text_view,
|
||||
NULL);
|
||||
|
||||
g_object_notify (G_OBJECT (text_view), "input-hints");
|
||||
gtk_text_view_update_emoji_action (text_view);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9891,3 +9979,44 @@ gtk_text_view_insert_emoji (GtkTextView *text_view)
|
||||
|
||||
gtk_popover_popup (GTK_POPOVER (chooser));
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_text_view_set_extra_menu:
|
||||
* @text_view: a #GtkTextView
|
||||
* @model: (allow-none): a #GMenuModel
|
||||
*
|
||||
* Sets a menu model to add when constructing
|
||||
* the context menu for @text_view.
|
||||
*/
|
||||
void
|
||||
gtk_text_view_set_extra_menu (GtkTextView *text_view,
|
||||
GMenuModel *model)
|
||||
{
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
|
||||
g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
|
||||
|
||||
if (g_set_object (&priv->extra_menu, model))
|
||||
{
|
||||
g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
|
||||
g_object_notify (G_OBJECT (text_view), "extra-menu");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_text_view_get_extra_menu:
|
||||
* @text_view: a #GtkTextView
|
||||
*
|
||||
* Gets the menu model set with gtk_text_view_set_extra_menu().
|
||||
*
|
||||
* Returns: (transfer none): (nullable): the menu model
|
||||
*/
|
||||
GMenuModel *
|
||||
gtk_text_view_get_extra_menu (GtkTextView *text_view)
|
||||
{
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
|
||||
|
||||
return priv->extra_menu;
|
||||
}
|
||||
|
@ -120,8 +120,6 @@ struct _GtkTextView
|
||||
/**
|
||||
* GtkTextViewClass:
|
||||
* @parent_class: The object class structure needs to be the first
|
||||
* @populate_popup: The class handler for the #GtkTextView::populate-popup
|
||||
* signal.
|
||||
* @move_cursor: The class handler for the #GtkTextView::move-cursor
|
||||
* keybinding signal.
|
||||
* @set_anchor: The class handler for the #GtkTextView::set-anchor
|
||||
@ -159,8 +157,6 @@ struct _GtkTextViewClass
|
||||
|
||||
/*< public >*/
|
||||
|
||||
void (* populate_popup) (GtkTextView *text_view,
|
||||
GtkWidget *popup);
|
||||
void (* move_cursor) (GtkTextView *text_view,
|
||||
GtkMovementStep step,
|
||||
gint count,
|
||||
@ -431,6 +427,12 @@ void gtk_text_view_set_monospace (GtkTextView *text_vi
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_text_view_get_monospace (GtkTextView *text_view);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_text_view_set_extra_menu (GtkTextView *text_view,
|
||||
GMenuModel *model);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GMenuModel * gtk_text_view_get_extra_menu (GtkTextView *text_view);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_TEXT_VIEW_H__ */
|
||||
|
Loading…
Reference in New Issue
Block a user