Confusion about mouse_button_pressed_event vs mouse_button_released_event vs mouse_moved_event

edited January 27 in Verification

Hello,

I'm attempting to create a plugin (in Klayout 0.30.5) that loads a second layout if some conditions are triggered, and want to be able to use the plugin there. I'm having a bit of trouble understanding the structure of plugins and when the mouse_button_pressed_event etc. are actually called.

Here's the relevant skeleton of my code:

# $description: test
# $show-in-menu
# $menu-path: tools_menu>end("Test").end

import pya as kl

class PluginTestFactory(kl.PluginFactory):
    def __init__(self):
        self.add_submenu("menu_name", "inset_pos", "title")
        self.register(29318, "plugin_name", "test_plugin")
    def create_plugin(self, manager, dispatcher, view):
        return PluginTest(view)
class PluginTest(kl.Plugin):
    def __init__(self,view):
            self.curr_annot = kl.Annotation()
            self.curr_annot.p1 = kl.DPoint(0,0)
            self.curr_annot.p2 = kl.DPoint(0,0)
            self.curr_annot.fmt = ""
            self.curr_annot.fmt_x = ""
            self.curr_annot.fmt_y = ""
            self.curr_annot.outline = self.curr_annot.OutlineBox
            self.curr_annot.style = self.curr_annot.StyleLine
            view.insert_annotation(self.curr_annot)
    def mouse_button_pressed_event(self, p, buttons, prio):
        print("hello")
    def mouse_button_released_event(self, p, buttons, prio):
        self.match()
    def mouse_moved_event(self, p, buttons, prio):
        self.curr_annot.p2 = p

    def match(self):
        big_menu = kl.QDialog()
        big_menu.layout = kl.QGridLayout()
        containing_layout = kl.QWidget()
        self.layoutwidg = kl.LayoutViewWidget(containing_layout)
        cv = self.layoutwidg.view().create_layout(True)
        viewing_layout = self.layoutwidg.view().cellview(cv).layout()
        big_menu.layout.addWidget(self.layoutwidg,0,0,1,1)
        big_menu.show()

if __name__ == "__main__":
    PluginTestFactory()

When I click on the Tools>test menu button, a "test_plugin" button appears on the toolbar. The annotation appears and follows my mouse around before pressing the test_plugin button; I expected the annotation to appear, but the fact that it follows my mouse around implies that the mouse_moved_event is being called implicitly.

When I click the button in the toolbar, the "Basic Editing" pane pops up (why does that happen, by the way? It's annoying.), but no other effects occur; clicking and dragging and releasing does nothing. But when I open the debugger, I can put a breakpoint on the mouse_button_pressed_event and the mouse_button_released_event lines and it stops at both of them. If I step past the self.match() call, nothing else happens.

But if I step into the self.match() call and let it run, it opens up the QDialog I asked for. Within this QDialog, it appears that the mouse_button_pressed_event is completely inactive, whereas the mouse_button_released_event and mouse_moved_event calls are still active (in the sense that it stops at a breakpoint in the mouse_button_released_event function).

This is all very confusing to me, and I would like to understand what's going on. Is there some sort of infinite recursion being headed off by avoiding the self.match() call when the debugger breakpoint is absent, and is that why it isn't run?

The simplest question that would move me forward is: How do I force the mouse_button_pressed_event to be recognized in the layout in this pop-up window?

Thanks so much!

Noah

Comments

  • Hi Noah,

    did you check the documentation?

    https://www.klayout.de/doc-qt5/programming/application_api.html#k_11 explains the concept of the mouse events. Specifically it says:

    If no receiver accepted the mouse event by returning true, it is sent again to all plugins with 'prio' set to false. Again, the loop terminates if one of the receivers returns true. The second pass gives inactive plugins a chance to monitor the mouse and implement specific actions - i.e. displaying the current position.

    Here is more example code: https://www.klayout.de/doc-qt5/code/class_PluginFactory.html

    There are packages implementing Plugins, for example this one: https://github.com/s910324/SplitShapePlugin. You can study that one for example. It is well made.

    Matthias

  • edited February 2

    Thanks for your reply! My two questions are:
    1. It says in the documentation that

    When a LayoutView is created, it will use the PluginFactory to create a specific Plugin instance for the view. When the tool bar button is pressed which relates to this plugin, the plugin will be activated and mouse or other events will be redirected to this plugin.

    However, when I open this new LayoutView in the pop-up window, there is no tool bar button to press, so the plugin never gets activated (and the Select tool appears to always be activated). If I re-click on the button in the main window, it doesn't seem to activate the plugin in the pop-up window. And if I manually call self.activated, nothing seems to happen. So I'm asking how to activate the plugin, either manually or through code, in this new LayoutView.
    2. It seems that in this case, the other mouse events are working as intended, but I cannot seem to get mouse_button_pressed_event to be called with prio=False. I saw at this link that there had been trouble in the past with mouse_button_pressed_event not working perfectly, so I'm concerned there might be a bug in its implementation, even though that link says it's been fixed.

    I am sorry because these seem like basic questions, but it feels like what I'm seeing is counter to the documentation. Thanks so much for your help!

    (P.S. I'm sorry I posted this in the Verification category; I could not figure out how to either delete the topic or change the category after it was posted.)

    Noah

  • So then let's start with a minimum example:

    import pya as kl
    
    class Discussion2841PluginFactory(kl.PluginFactory):
    
      def __init__(self):
        self.register(100000, "plugin_name", "test_plugin")
    
      def create_plugin(self, manager, dispatcher, view):
        return Discussion2841Plugin()
    
    class Discussion2841Plugin(kl.Plugin):
    
      def __init__(self):
        print("Plugin::__init__")
    
      def mouse_click_event(self, p, buttons, prio):
        print(f"Plugin::mouse_click_event {p} {buttons} {prio}")
        return True
    
      def mouse_double_click_event(self, p, buttons, prio):
        print(f"Plugin::mouse_double_click_event {p} {buttons} {prio}")
        return True
    
      def mouse_moved_event(self, p, buttons, prio):
        print(f"Plugin::mouse_moved_event {p} {buttons} {prio}")
        return True
    
      def mouse_button_pressed_event(self, p, buttons, prio):
        print(f"Plugin::mouse_button_pressed_event {p} {buttons} {prio}")
        return True
    
      def mouse_button_released_event(self, p, buttons, prio):
        print(f"Plugin::mouse_button_released_event {p} {buttons} {prio}")
        return True
    
    
    Discussion2841PluginFactory()
    

    If I run this example, I get a new button in the tool bar, named "test_plugin". That is the name, the plugin got in the "register" call (third parameter).

    While that function is not active, I receive calls to "mouse_button_moved" with prio = False. These are the background events that are sent to the plugin with low priority and because no other plugin claims them. If I activate the function by pressing the "test_plugin" function, I receive these events with prio = True. Unlike other plugins, mine claims these events by returning True from the "mouse_moved_event" callback. Because of these, these events are not available to other plugins and this is why for example the cursor position display freezes when the plugin is activated. Usually it's best not to claim the events and to return False.

    The same is true, when you click at one point - in that case, "mouse_click_event" calls are issued.

    Now to the "mouse_button_pressed_event" and "mouse_button_released_event": unlike Qt events for example, these events get issued only alternatively to the click event. A click event is issued, when you press the mouse and release the button on the same location. If you move the mouse in between pressing and releasing (a drag operation), you will receive a mouse button press event, some move events and then a mouse button release event. Since the application can't know what is going to happen, the mouse button press event is delayed into the first move operation.

    That concept is basis for all mouse-driven features inside KLayout and supports "click-move-click" (pick and place) and "press-move-release" (drag) scenarios. But you cannot get an immediate event on mouse button press, if that is what you are asking for.

    Matthias

  • edited February 9

    Hello Matthias,

    Again, thanks for your response and your helpful code. Can you verify the following for me in this plugin?

    1. When you run the plugin, before clicking the button, the mouse_button_pressed_event is triggered as it should be (with prio = False).
    2. When you then click the plugin, the mouse_button_pressed_event is triggered as it should be (with prio = True).
    3. When you then click the Select button on the toolbar, i.e. click OFF the plugin, the mouse_button_pressed_event is no longer triggered, even with prio = False.

    If this is the case, I think there is a bug that might need attending to.

  • No, that is not how it is intended.

    When the "test_plugin" is not activated, there is no mouse_click_event with prio=False because the active plugin (Select) claims that event.

    When the "test_plugin" is not activated, there is a mouse_button_pressed_event with prio=False, but only after you moved the mouse a little, so that it is considered a drag operation. The active plugin (Select) is not claiming that event, so it is delivered to the test plugin with prio=False. Pressing the button, moving the mouse, and releasing the button gives a sequence of events: mouse_button_pressed_event, mouse_moved_event (n times), mouse_button_released_event, all with prio=False.

    When the "test_plugin" is activated, when clicking a button, you receive a mouse_click_event with prio=True. When pressing the button and moving the mouse, you get a mouse_button_pressed_event, then mouse_moved_events and finally a mouse_button_released_event with prio=True, but only after you moved the mouse a little, so the events can be distinguished from a single click.

    Matthias

  • edited February 10

    Hello Matthias,

    I'm so sorry, I see what's happening now. So after running the script but before clicking the plugin button, Select is not claiming any of _clicked, _pressed, _released or _moved. But after clicking the plugin button and then re-clicking Select, it claims _clicked and _pressed, but still not _released or _moved. Is this correct? And that's why it's printing _released and _moved? Interesting that it doesn't claim _released.

  • Well, Select claims "clicked" and "pressed". Both because that is part of the functionality of "Select". A single click will select the object below the click, a "pressed" event will start dragging a selection rectangle. For some reason, the Select tool does not claim "moved" and "released" while it drags, but the main intention is to claim the two activation events, so it can do it's job in peace.

    Don't trust the events which you get with "prio=False". As a rule of thumb, "moved" should be available always, but it depends on the selected tool, if you receive activation events such as a mouse click. The main goal is to prevent duplicate actions on a single click.

    Only if the tool is active, you are first class citizen in the event system. Your tool will receive all events with "prio=True" and can decide if it claims them (return True) or allows other tools to see them with "prio=False" by returning False.

    KLayout is basically built around ~20 plugins that all use that scheme. The mouse tracker you see at the bottom is a plugin that simply listens to mouse move events with prio=False for example. The mouse tracker is also responsible for displaying a crosshair cursor if you enabled it.

    The zoom feature is a plugin that listens to mouse wheel events and mouse pressed events with prio=False to start dragging the zoom box. Because the Select tool claims only pressed events with the left mouse button, the zoom box will see right-click events with prio=False and can start dragging a zoom box. Because the zoom box claims mouse move events in that case and because the mouse tracker is after the zoom box in the plugin order, the mouse tracker and the crosshair cursor do not change while dragging a zoom box.

    The box drawing tool is a plugin that listens to click events with prio=True and starts a box drawing operation then. Some plugins do not handle mouse events at all, but only register menu items (like "Tools/Browse Shapes").

    So you see there is a ecosystem inhabited by a number of animals that collaborate using a simple standard protocol. Just make sure your plugin fits into that.

    Matthias

Sign In or Register to comment.