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.

Sign In or Register to comment.