frmdstryr / enamlx

Additional Qt Widgets for Enaml
MIT License
28 stars 9 forks source link

GraphicsView widgets don't notify of position changes from mouse interactions #20

Open jjennings955 opened 4 years ago

jjennings955 commented 4 years ago

I'm not sure if this is intended/known or not, but when items in a GraphicsView are set to movable, the position changes coming from mouse interactions don't get reflected/propagated through things that may be bound to item.position.

How can I achieve this?

Example code below

import math
import enamlx
enamlx.install()
from enamlx.widgets.api import (
    GraphicsView, GraphicsRectItem, GraphicsTextItem, Pen, Brush, GraphicsItemGroup
)
from enaml.widgets.api import (
    MainWindow, Container, PushButton, Label
)

enamldef Main(MainWindow): window:
    attr red_brush = Brush(color='red')
    initial_size = canvas.size
    Container:
        Label:
            text = "Mouse events don't propagate to the UI (the position text should be updated while dragging)"
        PushButton:
            text = "Set Position to (100, 100)"
            clicked :: group.position = (100, 100)
        GraphicsView: canvas:
            attr size = (960, 960)
            background = "#fff"
            GraphicsItemGroup: group:
                position = (300, 300)
                selectable = True
                movable = True
                GraphicsRectItem:
                    width = 200
                    height = 100
                    brush = red_brush
                    opacity = 0.5
                GraphicsTextItem: text:
                    text << str(group.position)
frmdstryr commented 4 years ago

If I remember correctly the signals need setup to do this, I don't remember if it was a feature flag that needs set for it to trigger the changes. See https://github.com/frmdstryr/enamlx/blob/master/enamlx/qt/qt_graphics_view.py#L943

Haven't worked on this in a while...

frmdstryr commented 4 years ago

See https://doc.qt.io/qt-5/qgraphicsitem.html#itemChange

Edit: I think there's a bug in https://github.com/frmdstryr/enamlx/blame/master/enamlx/qt/qt_graphics_view.py#L945, it should be if change ==

jjennings955 commented 4 years ago

That does seem to be a bug, but that function is never getting called for me.

I also tried messing with the flags. I believe you need to do self.widget.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)

for that to be fired, but I tried that and it doesn't work.

jjennings955 commented 4 years ago

I think the error is here: https://github.com/frmdstryr/enamlx/blame/master/enamlx/qt/qt_graphics_view.py#L935

Patching itemChange like that doesn't work.

I created a small example (pure PyQt5) below: The subclassed QGraphicsRectItem that implements itemChange works, but assigning to the method of an instance does not.

from PyQt5.QtGui import QBrush, QColor
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsRectItem, QMainWindow, QApplication

class ChangeableItem(QGraphicsRectItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setBrush(QBrush(QColor("green")))
        self.setFlags(QGraphicsRectItem.ItemSendsGeometryChanges|QGraphicsRectItem.ItemIsMovable)

    def itemChange(self, change, value):
        if change == QGraphicsRectItem.ItemPositionHasChanged:
            print("ChangeableItem", self.pos(), self.scenePos())
        return QGraphicsRectItem.itemChange(self, change, value)

class PatchedChangeableItem(QGraphicsRectItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setBrush(QBrush(QColor("red")))
        self.setFlags(QGraphicsRectItem.ItemSendsGeometryChanges|QGraphicsRectItem.ItemIsMovable)

app = QApplication([])
window = QMainWindow()
scene = QGraphicsScene(window)
view = QGraphicsView(scene)
view.setGeometry(0, 0, 500, 500)
scene.setSceneRect(0, 0, 500, 500)

rect_good = ChangeableItem(0, 0, 100, 100)
rect_bad = PatchedChangeableItem(100, 0, 100, 100)
def patchItemChange(self, change, value):
    if change == QGraphicsRectItem.ItemPositionHasChanged:
        print("PatchedChangeableItem", self.pos(), self.scenePos())
    return QGraphicsRectItem.itemChange(self, change, value)

rect_bad.itemChange = patchItemChange

scene.addItem(rect_good)
scene.addItem(rect_bad)
window.setCentralWidget(view)
window.resize(800, 800)
window.show()
app.exec_()
jjennings955 commented 4 years ago

I'm not sure exactly how/where to implement it, but I think rather than hooks, you can just implement the hooked version as the default, and use QGraphicsItem.setFlag to control which itemChange events are responded to?

frmdstryr commented 4 years ago

Thanks for looking into it, it sounds like each widget needs to be subclassed and the flag explicitly enabled.

jjennings955 commented 4 years ago

I have something mostly working. There were some other weird gotchas in there. I'll try to post it here later.

jjennings955 commented 4 years ago

My implementation "works" but it always segfaults. It seems to be something to do with the QT event loop. Do you have any idea what might cause this? Here's the backtrace from running it with gdb. It happens if I just move my mouse around a bunch.

Seems pretty far removed from the enaml/enamlx stuff, but I'm stumped and really have no idea how to debug it.

#1  0x000055555569b7e2 in _PyDict_LoadGlobal (globals=0x7fffec1905f0, builtins=0x7ffff7fc7910, key=0x7ffff7f4bf70)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/dictobject.c:1430
#2  0x0000555555714da9 in _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:2114
#3  0x000055555566f73b in function_code_fastcall (globals=<optimized out>, nargs=2, args=<optimized out>, co=0x7fffec195780)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:283
#4  0x000055555566f73b in _PyFunction_FastCallDict (func=<optimized out>, args=0x7fffffffbb00, nargs=2, kwargs=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:322
#5  0x000055555568a943 in _PyObject_Call_Prepend (callable=0x7fffec164710, obj=<optimized out>, args=0x7fffe0045f90, kwargs=0x0)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:908
#6  0x000055555567db9e in PyObject_Call (callable=0x7ffff5bd10a0, args=<optimized out>, kwargs=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:245
#7  0x00007ffff1cef481 in call_method (va=0x7fffffffbba8, fmt=0x7fffefa4a5c4 "D", method=0x7ffff5bd10a0) at siplib.c:2264
#8  0x00007ffff1cef481 in sip_api_call_procedure_method (gil_state=PyGILState_UNLOCKED, error_handler=0x7ffff51979e0 <sipVEH_QtCore_PyQt5(_sipSimpleWrapper*, PyGILState_STATE)>, py_self=0x7fffec0db910, method=0x7ffff5bd10a0, fmt=0x7fffefa4a5c4 "D") at siplib.c:2286
#9  0x00007fffef8fbe9d in sipVH_QtWidgets_12(PyGILState_STATE, void (*)(_sipSimpleWrapper*, PyGILState_STATE), _sipSimpleWrapper*, _object*, QMouseEvent*) () at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/QtWidgets.abi3.so
#10 0x00007fffef9ea20b in sipQGraphicsView::mouseMoveEvent(QMouseEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/QtWidgets.abi3.so
#11 0x00007fffef006550 in QWidget::event(QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#12 0x00007fffef0ae5ee in QFrame::event(QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#13 0x00007fffef2e606b in QGraphicsView::viewportEvent(QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#14 0x00007fffef9ec113 in sipQGraphicsView::viewportEvent(QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/QtWidgets.abi3.so
#15 0x00007ffff4b7c68d in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Core.so.5
#16 0x00007fffeefc79d2 in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#17 0x00007fffeefcf5e8 in QApplication::notify(QObject*, QEvent*) ()
---Type <return> to continue, or q <return> to quit---
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#18 0x00007fffefa3115e in sipQApplication::notify(QObject*, QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/QtWidgets.abi3.so
#19 0x00007ffff4b7c8f8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Core.so.5
#20 0x00007fffeefcdeca in QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool, bool) () at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#21 0x00007fffef01fcb1 in QWidgetWindow::handleMouseEvent(QMouseEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#22 0x00007fffef0228fb in QWidgetWindow::event(QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#23 0x00007fffeefc79fc in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#24 0x00007fffeefce980 in QApplication::notify(QObject*, QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Widgets.so.5
#25 0x00007fffefa3115e in sipQApplication::notify(QObject*, QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/QtWidgets.abi3.so
#26 0x00007ffff4b7c8f8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Core.so.5
#27 0x00007ffff0ac5fc8 in QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Gui.so.5
#28 0x00007ffff0ac74a5 in QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Gui.so.5
#29 0x00007ffff0aa3e3b in QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Gui.so.5
#30 0x00007fffee8cd08a in xcbSourceDispatch(_GSource*, int (*)(void*), void*) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/plugins/platforms/../../lib/libQt5XcbQpa.so.5
#31 0x00007ffff21c0417 in g_main_context_dispatch () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#32 0x00007ffff21c0650 in  () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#33 0x00007ffff21c06dc in g_main_context_iteration () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#34 0x00007ffff4bd53ec in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Core.so.5
#35 0x00007ffff4b7b312 in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) ()
---Type <return> to continue, or q <return> to quit---
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Core.so.5
#36 0x00007ffff4b84240 in QCoreApplication::exec() ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/Qt/lib/libQt5Core.so.5
#37 0x00007fffef8adf50 in meth_QApplication_exec_ ()
    at /home/jason/miniconda3/envs/node_enaml/lib/python3.7/site-packages/PyQt5/QtWidgets.abi3.so
#38 0x00005555556b5090 in _PyMethodDef_RawFastCallKeywords (method=0x7fffefd98340 <methods_QApplication+480>, self=0x7ffff17df4b0, args=0x7fffe0033a98, nargs=<optimized out>, kwnames=<optimized out>) at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:698
#39 0x00005555556b5231 in _PyCFunction_FastCallKeywords (func=0x7fffe001bfa0, args=<optimized out>, nargs=<optimized out>, kwnames=<optimized out>) at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:734
#40 0x0000555555719a5d in call_function (kwnames=0x0, oparg=0, pp_stack=<synthetic pointer>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:4568
#41 0x0000555555719a5d in _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3093
#42 0x00005555556b468b in function_code_fastcall (globals=<optimized out>, nargs=1, args=<optimized out>, co=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:283
#43 0x00005555556b468b in _PyFunction_FastCallKeywords (func=<optimized out>, stack=0x555555aaab90, nargs=1, kwnames=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:408
#44 0x0000555555715260 in call_function (kwnames=0x0, oparg=<optimized out>, pp_stack=<synthetic pointer>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:4616
#45 0x0000555555715260 in _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3110
#46 0x00005555556b468b in function_code_fastcall (globals=<optimized out>, nargs=0, args=<optimized out>, co=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:283
#47 0x00005555556b468b in _PyFunction_FastCallKeywords (func=<optimized out>, stack=0x7ffff7e59d60, nargs=0, kwnames=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:408
#48 0x0000555555714fd6 in call_function (kwnames=0x0, oparg=<optimized out>, pp_stack=<synthetic pointer>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:4616
#49 0x0000555555714fd6 in _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3124
#50 0x000055555566e6f9 in _PyEval_EvalCodeWithName (_co=0x7ffff6407f60, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=0x0, kwargs=0x0, kwcount=0, kwstep=2, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0, name=0x0, qualname=0x0) at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3930
#51 0x000055555566f5f4 in PyEval_EvalCodeEx (_co=<optimized out>, globals=<optimized out>, locals=<optimized out>, args=<optimized out>,---Type <return> to continue, or q <return> to quit---
 argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3959
#52 0x000055555566f61c in PyEval_EvalCode (co=<optimized out>, globals=<optimized out>, locals=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:524
#53 0x00005555557248bd in builtin_exec_impl.isra.12 (locals=0x7ffff7f40f00, globals=0x7ffff7f40f00, source=0x7ffff6407f60)
    at /tmp/build/80754af9/python_1578510683607/work/Python/bltinmodule.c:1079
#54 0x00005555557248bd in builtin_exec (module=<optimized out>, args=<optimized out>, nargs=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/clinic/bltinmodule.c.h:283
#55 0x00005555556b4f99 in _PyMethodDef_RawFastCallKeywords (method=0x5555558702e0 <builtin_methods+480>, self=0x7ffff7fc0d10, args=0x555555998560, nargs=<optimized out>, kwnames=<optimized out>) at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:655
#56 0x00005555556b5231 in _PyCFunction_FastCallKeywords (func=0x7ffff7fc7e10, args=<optimized out>, nargs=<optimized out>, kwnames=<optimized out>) at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:734
#57 0x0000555555719324 in call_function (kwnames=0x0, oparg=2, pp_stack=<synthetic pointer>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:4568
#58 0x0000555555719324 in _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3124
#59 0x000055555566e6f9 in _PyEval_EvalCodeWithName (_co=0x7ffff63dd780, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=0x0, kwargs=0x55555593df10, kwcount=0, kwstep=1, defs=0x7ffff6409e48, defcount=5, kwdefs=0x0, closure=0x0, name=0x7ffff63f2eb0, qualname=0x7ffff63f2eb0) at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3930
#60 0x00005555556b48b5 in _PyFunction_FastCallKeywords (func=<optimized out>, stack=0x55555593dee8, nargs=5, kwnames=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:433
#61 0x0000555555714fd6 in call_function (kwnames=0x0, oparg=<optimized out>, pp_stack=<synthetic pointer>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:4616
#62 0x0000555555714fd6 in _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3124
#63 0x000055555566e6f9 in _PyEval_EvalCodeWithName (_co=0x7ffff63d1660, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=0x0, kwargs=0x0, kwcount=0, kwstep=2, defs=0x7ffff7ec1fa8, defcount=1, kwdefs=0x0, closure=0x0, name=0x7ffff63f3710, qualname=0x7ffff63f3710) at /tmp/build/80754af9/python_1578510683607/work/Python/ceval.c:3930
#64 0x000055555566f805 in _PyFunction_FastCallDict (func=<optimized out>, args=0x7ffff64383d8, nargs=2, kwargs=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Objects/call.c:376
#65 0x0000555555769837 in pymain_run_module (modname=<optimized out>, set_argv0=1)
    at /tmp/build/80754af9/python_1578510683607/work/Modules/main.c:327
#66 0x000055555577bfcb in pymain_run_python (pymain=0x7fffffffd740)
---Type <return> to continue, or q <return> to quit---
    at /tmp/build/80754af9/python_1578510683607/work/Modules/main.c:2871
#67 0x000055555577bfcb in pymain_main (pymain=0x7fffffffd740) at /tmp/build/80754af9/python_1578510683607/work/Modules/main.c:3414
#68 0x000055555577c0bc in _Py_UnixMain (argc=<optimized out>, argv=<optimized out>)
    at /tmp/build/80754af9/python_1578510683607/work/Modules/main.c:3449
#69 0x00007ffff77e6b97 in __libc_start_main (main=
    0x5555556500a0 <main>, argc=4, argv=0x7fffffffd898, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd888) at ../csu/libc-start.c:310
#70 0x0000555555724990 in _start () at ../sysdeps/x86_64/elf/start.S:103
frmdstryr commented 4 years ago

Without any code its hard to tell but its most likely trying to load a ref that was already destroyed.