Compare commits

...

2 Commits

Author SHA1 Message Date
Arjan Molenaar
4a3fe0e3c0 macos: Actions from menu also work for GTK widgets
If we have a menu item, once would expect it would
work. Therefore, if we deal with a GTK window, we
need activate the action on the focused widget of the
active window.
2024-11-10 19:52:49 +01:00
Arjan Molenaar
8227a3b772 macos: Add a default Edit menu
This makes sure that keyboard shortcuts (copy/paste/select-all/etc.)
work with native windows too.

Windows like SavePanel and OpenPanel need menu items with shortcuts
to support features like copy, undo, and select-all.
2024-11-09 22:25:18 +01:00
2 changed files with 207 additions and 8 deletions

View File

@@ -22,7 +22,9 @@
#include "gtkapplicationprivate.h"
#include "gtkbuilder.h"
#include "gtknative.h"
#import <Cocoa/Cocoa.h>
#include <gdk/macos/GdkMacosWindow.h>
typedef struct
{
@@ -126,6 +128,49 @@ G_DEFINE_TYPE (GtkApplicationImplQuartz, gtk_application_impl_quartz, GTK_TYPE_A
}
@end
static GtkWindow*
gtk_application_impl_quartz_find_window(GdkMacosWindow *keyWindow)
{
GListModel *top_levels = gtk_window_get_toplevels ();
for (guint i = 0; i < g_list_model_get_n_items (top_levels); i++)
{
GtkWindow *win = (GtkWindow *) g_list_model_get_item (top_levels, i);
if (gtk_native_get_surface (GTK_NATIVE (win)) == (GdkSurface *) [keyWindow gdkSurface])
return win;
}
return NULL;
}
static void
gtk_application_impl_quartz_send_action (const gchar *action_name, SEL action)
{
static guint semaphore = 0;
NSWindow *keyWindow = [NSApp keyWindow];
if (keyWindow == NULL)
return;
if (semaphore == 0 && [keyWindow isKindOfClass:[GdkMacosWindow class]])
{
GtkWindow *window = gtk_application_impl_quartz_find_window ((GdkMacosWindow *) keyWindow);
if (window)
{
GtkWidget *focus = gtk_window_get_focus (window);
if (focus)
{
semaphore += 1;
gtk_widget_activate_action (focus, action_name, NULL);
semaphore -= 1;
}
}
}
else
[NSApp sendAction:action to:nil from:NSApp];
}
/* these exist only for accel handling */
static void
gtk_application_impl_quartz_hide (GSimpleAction *action,
@@ -151,12 +196,84 @@ gtk_application_impl_quartz_show_all (GSimpleAction *action,
[NSApp unhideAllApplications:NSApp];
}
static GActionEntry gtk_application_impl_quartz_actions[] = {
static GActionEntry gtk_application_impl_quartz_internal_actions[] = {
{ "hide", gtk_application_impl_quartz_hide },
{ "hide-others", gtk_application_impl_quartz_hide_others },
{ "show-all", gtk_application_impl_quartz_show_all }
};
static void
gtk_application_impl_quartz_undo (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
gtk_application_impl_quartz_send_action ("text.undo", @selector(undo:));
}
static void
gtk_application_impl_quartz_redo (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
gtk_application_impl_quartz_send_action ("text.redo", @selector(redo:));
}
static GActionEntry gtk_application_impl_quartz_text_actions[] = {
{ "undo", gtk_application_impl_quartz_undo },
{ "redo", gtk_application_impl_quartz_redo }
};
static void
gtk_application_impl_quartz_cut (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
gtk_application_impl_quartz_send_action ("clipboard.cut", @selector(cut:));
}
static void
gtk_application_impl_quartz_copy (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
gtk_application_impl_quartz_send_action ("clipboard.copy", @selector(copy:));
}
static void
gtk_application_impl_quartz_paste (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
gtk_application_impl_quartz_send_action ("clipboard.paste", @selector(paste:));
}
static GActionEntry gtk_application_impl_quartz_clipboard_actions[] = {
{ "cut", gtk_application_impl_quartz_cut },
{ "copy", gtk_application_impl_quartz_copy },
{ "paste", gtk_application_impl_quartz_paste }
};
static void
gtk_application_impl_quartz_delete (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
gtk_application_impl_quartz_send_action ("selection.delete", @selector(delete:));
}
static void
gtk_application_impl_quartz_select_all (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
gtk_application_impl_quartz_send_action ("selection.select-all", @selector(selectAll:));
}
static GActionEntry gtk_application_impl_quartz_selection_actions[] = {
{ "delete", gtk_application_impl_quartz_delete },
{ "select-all", gtk_application_impl_quartz_select_all }
};
static void
gtk_application_impl_quartz_set_app_menu (GtkApplicationImpl *impl,
GMenuModel *app_menu)
@@ -185,11 +302,19 @@ gtk_application_impl_quartz_startup (GtkApplicationImpl *impl,
gboolean register_session)
{
GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
GSimpleActionGroup *gtkinternal;
GSimpleActionGroup *group;
GMenuModel *menubar;
const char *pref_accel[] = {"<Meta>comma", NULL};
const char *hide_others_accel[] = {"<Meta><Alt>h", NULL};
const char *hide_accel[] = {"<Meta>h", NULL};
const char *quit_accel[] = {"<Meta>q", NULL};
const char *undo_accel[] = {"<Meta>z", NULL};
const char *redo_accel[] = {"<Meta><Shift>z", NULL};
const char *cut_accel[] = {"<Meta>x", NULL};
const char *copy_accel[] = {"<Meta>c", NULL};
const char *paste_accel[] = {"<Meta>v", NULL};
const char *delete_accel[] = {"Delete", NULL};
const char *select_all_accel[] = {"<Meta>a", NULL};
if (register_session)
{
@@ -205,13 +330,38 @@ gtk_application_impl_quartz_startup (GtkApplicationImpl *impl,
gtk_application_set_accels_for_action (impl->application, "gtkinternal.hide-others", hide_others_accel);
gtk_application_set_accels_for_action (impl->application, "gtkinternal.hide", hide_accel);
gtk_application_set_accels_for_action (impl->application, "app.quit", quit_accel);
gtk_application_set_accels_for_action (impl->application, "text.undo", undo_accel);
gtk_application_set_accels_for_action (impl->application, "text.redo", redo_accel);
gtk_application_set_accels_for_action (impl->application, "clipboard.cut", cut_accel);
gtk_application_set_accels_for_action (impl->application, "clipboard.copy", copy_accel);
gtk_application_set_accels_for_action (impl->application, "clipboard.paste", paste_accel);
gtk_application_set_accels_for_action (impl->application, "selection.delete", delete_accel);
gtk_application_set_accels_for_action (impl->application, "selection.select-all", select_all_accel);
/* and put code behind the 'special' accels */
gtkinternal = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (gtkinternal), gtk_application_impl_quartz_actions,
G_N_ELEMENTS (gtk_application_impl_quartz_actions), quartz);
gtk_application_insert_action_group (impl->application, "gtkinternal", G_ACTION_GROUP (gtkinternal));
g_object_unref (gtkinternal);
group = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (group), gtk_application_impl_quartz_internal_actions,
G_N_ELEMENTS (gtk_application_impl_quartz_internal_actions), quartz);
gtk_application_insert_action_group (impl->application, "gtkinternal", G_ACTION_GROUP (group));
g_object_unref (group);
group = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (group), gtk_application_impl_quartz_text_actions,
G_N_ELEMENTS (gtk_application_impl_quartz_text_actions), quartz);
gtk_application_insert_action_group (impl->application, "text", G_ACTION_GROUP (group));
g_object_unref (group);
group = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (group), gtk_application_impl_quartz_clipboard_actions,
G_N_ELEMENTS (gtk_application_impl_quartz_clipboard_actions), quartz);
gtk_application_insert_action_group (impl->application, "clipboard", G_ACTION_GROUP (group));
g_object_unref (group);
group = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (group), gtk_application_impl_quartz_selection_actions,
G_N_ELEMENTS (gtk_application_impl_quartz_selection_actions), quartz);
gtk_application_insert_action_group (impl->application, "selection", G_ACTION_GROUP (group));
g_object_unref (group);
/* now setup the menu */
if (quartz->standard_app_menu == NULL)
@@ -231,7 +381,19 @@ gtk_application_impl_quartz_startup (GtkApplicationImpl *impl,
gtk_application_impl_quartz_set_app_menu (impl, quartz->standard_app_menu);
/* This may or may not add an item to 'combined' */
gtk_application_impl_set_menubar (impl, gtk_application_get_menubar (impl->application));
menubar = gtk_application_get_menubar (impl->application);
if (menubar == NULL)
{
GtkBuilder *builder;
/* Provide a fallback menu, so keyboard shortcuts work in native windows too.
*/
builder = gtk_builder_new_from_resource ("/org/gtk/libgtk/ui/gtkapplication-quartz.ui");
menubar = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "default-menu")));
g_object_unref (builder);
}
gtk_application_impl_set_menubar (impl, menubar);
/* OK. Now put it in the menu. */
gtk_application_impl_quartz_setup_menu (G_MENU_MODEL (quartz->combined), quartz->muxer);

View File

@@ -45,4 +45,41 @@
</item>
</section>
</menu>
<menu id="default-menu">
<submenu>
<attribute name="label" translatable="yes">_Edit</attribute>
<section>
<item>
<attribute name="label" translatable="yes">Undo</attribute>
<attribute name="action">text.undo</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Redo</attribute>
<attribute name="action">text.redo</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Cut</attribute>
<attribute name="action">clipboard.cut</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Copy</attribute>
<attribute name="action">clipboard.copy</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Paste</attribute>
<attribute name="action">clipboard.paste</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Delete</attribute>
<attribute name="action">selection.delete</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Select All</attribute>
<attribute name="action">selection.select-all</attribute>
</item>
</section>
</submenu>
</menu>
</interface>