French keyboard issue in grasshopper

Hi,
I guess with an english keyboard this problem is rarely to come up.
But with my french layout I have to use right alt key as a modifier for characters like “ { } @ …”
When I want to write “{0;1}” inn input “P” of node Tree Branch by example :

  • I open context menu,
  • click Set Path,
  • click in text box,
  • hit right alt … It kicks me out.

It happens as soon as I need a special character (node name, group name, set data items, …)
My workaround until now is two copy the special character in a text editor and paste it.
I’m sure you understand my pain…

Is there something I can do to disable the alt “hint” option ?

regards

note: OS : Windows 10 pro x64.

image

Hi,

I’m using a Swiss/French keyboard layout, which means that I also use the ALT + to write curly braces, square brackets, etc. The latest Rhino for Mac doesn’t seem to have that problem though. The “hint feature” must not be implemented for macOS.

I think it’s a Mac / pc thing. French PCs have different alt keys left and right.

Hi,
Someone can tell me if this can be resolved ?
Is there any hope to have an option to enable/disable alt-key hint behavior to allow
special characters to be used normally ?

Regards

Hi - As far as I know, the situation hasn’t changed since last time this was discussed:

-wim

FilterAltEvent.zip (75.7 KB)

Should work as a workaround. But I don’t have a keyboard with AltGr so I cannot test it. On my PC it will filter all Alt keys. It is still kind of buggy sometimes. Here’s the code. It uses tons of Reflection and Harmony hook to filter events :wink:.

The code below is licensed under Apache Licene 2.0

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using Grasshopper;
using Grasshopper.GUI;
using Harmony;

namespace PancakeDev.Replacement
{
    public static class CtrlAltOverride
    {
        private const Keys ModifierKey = Keys.Alt;
        private static readonly HashSet<ToolStripDropDown> MenuStrips = new HashSet<ToolStripDropDown>();
        private static bool InCleanup = false;

        private static void AddStripMenu(ToolStripDropDown dropdown)
        {
            if (InCleanup)
                return;
            MenuStrips.Add(dropdown);
        }

        private static void RemoveStripMenu(ToolStripDropDown strip, bool removeEventOnly = false)
        {
            strip.Closing -= EvtMenuClosing;
            strip.Closed -= EvtMenuClosed;

            if (strip is ContextMenuStrip context)
                context.Opened -= EvtMenuOpened;

            if (!removeEventOnly && !InCleanup)
                MenuStrips.Remove(strip);
        }

        private static bool CheckKeys(Keys key)
        {
            return (key & ModifierKey) == ModifierKey;
        }

        private static bool CheckContextMenuVisible()
        {
            return MenuStrips.Any(c => c.Visible);
        }

        private static void EvtMenuOpened(object sender, EventArgs e)
        {
            var strip = (ContextMenuStrip)sender;
            foreach (var it in strip.Items)
            {
                if (it is ToolStripDropDownItem dropItem && dropItem.HasDropDownItems)
                {
                    dropItem.DropDown.Closing += EvtMenuClosing;
                    dropItem.DropDown.Closed += EvtMenuClosed;
                    AddStripMenu(dropItem.DropDown);
                }
            }
        }

        private static void EvtMenuClosing(object sender, ToolStripDropDownClosingEventArgs e)
        {
            if (e.CloseReason == ToolStripDropDownCloseReason.Keyboard && CheckKeys(Control.ModifierKeys))
            {
                e.Cancel = true;
            }
        }

        private static void EvtMenuClosed(object sender, ToolStripDropDownClosedEventArgs e)
        {
            if (sender is ToolStripDropDown strip)
                RemoveStripMenu(strip);
        }

        private static void PostfixMenuCtor(object __instance)
        {
            var strip = (ContextMenuStrip)__instance;
            strip.Opened += EvtMenuOpened;
            strip.Closing += EvtMenuClosing;
            strip.Closed += EvtMenuClosed;

            AddStripMenu(strip);
        }

        private static bool PrefixKeyDownEvent(object sender, KeyEventArgs e)
        {
            if (CheckKeys(e.Modifiers) && CheckContextMenuVisible())
            {
                e.Handled = true;
                return false;
            }
            return true;
        }

        private static void FallbackCleanup(object sender, MouseEventArgs e)
        {
            InCleanup = true;

            foreach(var it in MenuStrips)
            {
                RemoveStripMenu(it, true);

                if (!(it is ContextMenuStrip))
                    continue;

                it.Close();
            }

            MenuStrips.Clear();
            InCleanup = false;
        }

        public static bool ProcessHook(HarmonyInstance harmony)
        {
            var ghTypeMenu = typeof(Grasshopper.GUI.Canvas.GH_Capsule).Assembly.GetTypes().FirstOrDefault(
                t => t.FullName == "Grasshopper.GUI.Canvas.GH_CanvasMenuStrip");
            var ghMethodMenuCtor = ghTypeMenu?.GetConstructor(new Type[] { });
            var ghMethodKeyDownEvent = typeof(GH_DocumentEditor).GetMethod("EditorKeyDown",
                BindingFlags.Instance | BindingFlags.NonPublic);

            var methodRegisterContextMenu = typeof(CtrlAltOverride).GetMethod(nameof(PostfixMenuCtor),
                BindingFlags.Static | BindingFlags.NonPublic);
            var methodKeyDownEvent = typeof(CtrlAltOverride).GetMethod(nameof(PrefixKeyDownEvent),
                BindingFlags.Static | BindingFlags.NonPublic);

            if (ghMethodMenuCtor == null || ghMethodKeyDownEvent == null)
                return false;

            harmony.Patch(ghMethodMenuCtor, postfix: new HarmonyMethod(methodRegisterContextMenu));
            harmony.Patch(ghMethodKeyDownEvent, prefix: new HarmonyMethod(methodKeyDownEvent));

            Instances.DocumentEditor.KeyPreview = true;
            Instances.ActiveCanvas.MouseClick += FallbackCleanup;

            return true;
        }
    }
}

1 Like

Hi,
Thx for the sharing !
It seems to work :wink:
Unfortunalety I can’t figure out yet where this code should end up if I want to compile it.
Is it meant to be in a grasshopper plugin ?
Is there an entry point ? Where should it be invoked/registered ?

Regards.

PS: I tried the following and It works. But the “PancakeDev” menu does not appear. Which is fine. But I wonder if I my method is good : FilterAppEvent.cs (4.4 KB)

The menu is just a hint that the plugin is correctly loaded since it doesn’t have any other UI indications. :wink:

Use CanvasCreated event to initialize the hook, such as:

Instances.CanvasCreated += x => {
                MemoryLoadPatch.PatchMemoryLoad();
                Hub.StartupHook();
            };

Really useful !
Thank you.