Sublime Forum

How to reimplement a view's doubleclick event?

#1

I got a view which contains lines such as:

[X] Option1
[ ] Option2
[X] Option3
[X] Option4
...
[ ] OptionN

Now, I’d like to check/uncheck these lines when double clicking on them, would it be possible to reimplement the double click view’s event? Or if not possible, maybe overriding ctrl+left_mouse_button event?

Thx in advance

0 Likes

#2

One way you COULD do this would be to add an override on the sublime-mousemap file. For example, the Default/Default (Linux).sublime-mousemap has this entry for double clicks:

    {
        "button": "button1", "count": 2,
        "press_command": "drag_select",
        "press_args": {"by": "words"}
    },

However, the emphasis on COULD is because you SHOULD NOT do something like that unless you’re making the modification locally for your own use; the mouse map doesn’t include the ability to provide a context, which means that if you do this you’re stealing every double click everywhere all the time, whether it’s in your own custom file or not.

That means that (in this case) the user loses their ability to double click on text in order to select it. A potential solution to that would be to load all of the mouse maps, try to figure out exactly what would have happened if you hadn’t boosted the binding, and then do it if it’s not in your own file, but that’s a fair amount of work.

A potential workaround is something along the lines of this snippet:

class HyperhelpEventListener(sublime_plugin.EventListener):
    def on_text_command(self, view, command, args):
        if (view.is_read_only() and command == "drag_select" and
                args.get("by", None) == "words"):
            event = args["event"]
            point = view.window_to_text((event["x"], event["y"]))

            if view.match_selector(point, "text.hyperhelp meta.link"):
                view.window().run_command("hyperhelp_navigate",
                                         {"nav": "follow_link"})
                return ("noop")

        return None

This catches the text command that the default mouse map is triggering and checks the position the click happened at to see what to do. If the click is inside of a read only file on a help link, it dispatches a command to follow the link and then rewrites the text command to do nothing. Otherwise, it falls through and the command runs as normal.

In your case you would instead determine if the cursor is in a view of your own creation (possibly within the brackets or some such?) and trigger that way. Since you’re going to modify the buffer by changing the option, instead of invoking a command and then rewriting it to be noop, you could just rewrite the command to be your toggle command directly, since it would be a text command doing that.

This won’t work if the user has modified their own mouse map (or some other naughty package has done it for them) in a way that stops this command from being triggered, though.

I would recommend as a fall back you also have a key binding(s) for this, like for example Spacebar and/or Enter with a context that only triggers when the cursor is in an appropriate place, so that the user can use the keyboard as well, which will always work.

That’s just a good design idea in the general case, I think, since many people try to leave the mouse be as much as is possible and would probably thus welcome the ability to do this with a key binding as well.

1 Like

#3

@OdatNurd Hiya! As usual, thanks for such an elaborated answer, I hadn’t considered EventListener as an option. One question remains though, imagine I got this little piece of code:

def on_text_command(self, view, command, args):
    if command == "drag_select" and is_valid_view(view):
        event = args["event"]
        point = view.window_to_text((event["x"], event["y"]))
        line = view.substr(view.line(point))
        word = view.substr(view.word(point))
        new_line = toggle_line(line)

Let’s say new_line is the modified line I’d like to replace line with, ie: if line="[X] Option1" then new_line="[ ] Option1". How can i replace the line region with new_line? Asking this cos after checking the docs I just see sublime.view.replace(edit, region, string) method, which can be used on TextCommand subclasses, as it gives you the edit object… But in the EventListener.on_text_command, I don’t understand how you can’t get the edit manually. In fact, the docs says:

Edit objects are passed to TextCommands, and can not be created by the user. Using an invalid Edit object, or an Edit object from a different View, will cause the functions that require them to fail.

So… any idea?

Thanks in advance!

0 Likes

#4

You would create a custom TextCommand doing exactly what you need (specified by arguments) and invoke that from within your event listener.

2 Likes

#5

Sort of unrelated to the main question, which was about handling double click events. But at the end I’ve just implemented a TextCommand which toggles lines living in several selections, at the moment you decide to use multiselection to toogle lines using double_click events to toggle lines doesn’t make sense anymore. Anyway, guess @OdatNurd’s answer is the one who solved the main thread question, which was about handling double click.

0 Likes