Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support secondary arguments support to load_ui #81

Closed
wants to merge 65 commits into from
Closed

Add support secondary arguments support to load_ui #81

wants to merge 65 commits into from

Conversation

fredrikaverpil
Copy link
Collaborator

@fredrikaverpil fredrikaverpil commented Jul 28, 2016

Fix #80

We can now support the base instance argument and the custom widgets argument in load_ui thanks to the custom class in this gist.

Let's discuss.

How do we deal with the copyright message at the top of the gist?
We should give Sebastian Wiesner (lunaryorn@gmail.com) credit for this.

@bpabel - do you know how to use the third (custom widgets) argument?
I've added support for it but I'm not sure about its use case. I'd like to see an example of it being used so that I can add tests for it.

@mottosso I was unable to remove QStatusBar and QMenuBar from the tests UI without crash.

@bpabel
Copy link

bpabel commented Jul 29, 2016

In that example, it would be a dictionary of class name: class objects. I think the most common use is to just import all the custom widget classes into your module and then just pass globals() in as the customWidget argument. In the .ui file, you would use the promote to feature in Qt Designer to specify the class name for the custom widget.

There might be a better way to do this though, because PyQt doesn't require the customWidgets dictionary, and is able to pull the import path from the .ui file. I believe there is a convention where you set the header file attribute to the import path for the class, and loadUi will try to load the custom widget class using that import path.

I can test and make an example, but probably not until the weekend.

@fredrikaverpil
Copy link
Collaborator Author

I can test and make an example, but probably not until the weekend.

That would be great, thanks. 👍

@mottosso
Copy link
Owner

What are our thoughts on this?

Hi list,

We've recently noticed that our current QUILoader implementation is
broken in subtle ways, and fixing the issue will in practice require a
complete rewrite of the functionality in Python.

We're prepared to do the rewrite, but this will be post-1.0 content for
us. Hence, we recommend PySide developers to refrain from using
QUILoader for the time being. Instead, you can use pyside-uic to convert
the .ui files to Python and use them that way.

Sorry for any inconvenience.

Cheers,

ma.

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Jul 30, 2016

What are our thoughts on this?

Hm. That seems almost six years old and this is the first time I've seen this being mentioned. I wonder if we can find any info on whatever issues they're referring to ever got fixed. I'm noticing they refer to QUILoader while today this is actually called QUiLoader ... perhaps that's just a coincidence, or it was rewritten and renamed to indicate this change?

To be honest, I've always used pysideuic in the past to mimic the loadUI.uic behavior of PyQt4. I wasn't aware of this subclassing method which is in this PR. However, I'm using QUiLoader now (since I'm using the load_ui function of Qt.py). So far I haven't had any issues.

The problem with pysideuic is that e.g. The Foundry decided to not bundle that module with Nuke. I contacted the Foundry and asked them to do that a long time ago, and their response was I should use QUiLoader. At the time I didn't do that and instead I made sure pysideuic was available to Nuke.

I'd like to propose the following: If we see issues with QUiLoader, we could make Qt.py use pysideuic first, if that is available, and if not Qt.py would fall back onto QUiLoader. But I'd like to give it some time to see if that's really necessary before implementing something like that. From what I've read on mailing lists and over at e.g. Stackoverflow, QUiLoader is the go-to mechanism to load UIs in PySide.

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Jul 30, 2016

It seems at least some bug fixing activity was made after 2010:

In the Google Groups post, bug 552 ("Segmentation fault when using QUiLoader and QTabWidget") was mentioned, which they have an old test for, created in 2012:
https://github.com/PySide/PySide/blob/bcf6058e9d0e8513f58a0f1062b428ea7eb03f0c/tests/QtUiTools/bug_552.py

Also, they say they added a warning to the documentation on QUiLoader:
https://groups.google.com/d/msg/pyside/_s1HPe6XTZs/WpmIsUeVmQ0J

This warning is nowhere to be seen today:
http://pyside.github.io/docs/pyside/PySide/QtUiTools/QUiLoader.html
... so I'm starting to lean towards that those issues were fixed.

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Jul 30, 2016

I can test and make an example, but probably not until the weekend.

@bpabel In case you have the possibility to write an example, that would be great. We've written our tests around load_ui like this:

def test_load_ui_into_self_pyside():
    """load_ui: Load widgets into self using PySide"""

    with ui() as fname:
        with pyside():
            from Qt import QtWidgets, load_ui

            class MainWindow(QtWidgets.QMainWindow):
                def __init__(self, parent=None):
                    QtWidgets.QMainWindow.__init__(self, parent)
                    load_ui(fname, self)

            app = QtWidgets.QApplication(sys.argv)
            window = MainWindow()

            # Inherited from .ui file
            assert hasattr(window, "pushButton")

You can view all 4 tests here.

We would need one test for PySide and one test for PyQt4 on the custom widgets attribute.

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Jul 31, 2016

@mottosso For some reason, I'm getting issues with the docker tests with this PR. Do you have any idea why that is – and, do you see this on your end as well?

Travis doesn't get this error.

➜ ~/code/repos/Qt.py (feature/load_ui) docker run --rm -v $(pwd):/Qt.py mottosso/qt.py
Doctest: test_caveats.test_1_qtgui_qabstractitemmodel_createindex ... ok
Doctest: test_caveats.test_2_qtgui_qabstractitemmodel_createindex ... ok
Doctest: test_caveats.test_3_qtcore_qitemselection ... ok
Doctest: test_caveats.test_4_qtcore_qitemselection ... ok
Doctest: test_caveats.test_5_qtcore_qitemselection ... ok
Doctest: test_caveats.test_6_qtcore_qitemselection ... ok
Doctest: test_caveats.test_7_qtcore_slot ... ok
Doctest: test_caveats.test_8_qtcore_slot ... ok
Doctest: test_caveats.test_9_qtwidgets_qaction_triggered ... ok
Doctest: test_caveats.test_10_qtwidgets_qaction_triggered ... ok
Tests require PySide and PyQt4 bindings to be installed ... ok
Setting QT_PREFERRED_BINDING properly forces a particular binding ... ok
Setting QT_PREFERRED_BINDING to more than one binding excludes others ... ok
Preferring None shouldn't import anything ... ok
Qt.py may be use alongside the actual binding ... ok
Preferred binding PyQt4 should have sip version 2 ... ok
Qt.py may be bundled along with another library/project ... ok
load_ui: Load widgets into self using PySide ... ERROR
load_ui: Load widgets into self using PyQt4 ... ERROR
load_ui: Load .ui file ... ERROR
load_ui: Load widgets into custom using PyQt4 ... ERROR
Raise ImportError if sip API v1 was already set (Python 2.x only) ... ok

======================================================================
ERROR: load_ui: Load widgets into self using PySide
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 133, in run
    self.runTest(result)
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 151, in runTest
    test(result)
  File "/usr/local/lib/python2.7/dist-packages/nosepipe.py", line 179, in __call__
    (header + data).decode("latin1"))
Exception: short message body (want 1852797797, got 35)
Something went wrong
Message: nosetests: cannot connect to X server 


======================================================================
ERROR: load_ui: Load widgets into self using PyQt4
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 133, in run
    self.runTest(result)
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 151, in runTest
    test(result)
  File "/usr/local/lib/python2.7/dist-packages/nosepipe.py", line 179, in __call__
    (header + data).decode("latin1"))
Exception: short message body (want 1852797797, got 35)
Something went wrong
Message: nosetests: cannot connect to X server 


======================================================================
ERROR: load_ui: Load .ui file
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 133, in run
    self.runTest(result)
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 151, in runTest
    test(result)
  File "/usr/local/lib/python2.7/dist-packages/nosepipe.py", line 179, in __call__
    (header + data).decode("latin1"))
Exception: short message body (want 1852797797, got 35)
Something went wrong
Message: nosetests: cannot connect to X server 


======================================================================
ERROR: load_ui: Load widgets into custom using PyQt4
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 133, in run
    self.runTest(result)
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 151, in runTest
    test(result)
  File "/usr/local/lib/python2.7/dist-packages/nosepipe.py", line 179, in __call__
    (header + data).decode("latin1"))
Exception: short message body (want 1852797797, got 35)
Something went wrong
Message: nosetests: cannot connect to X server 


----------------------------------------------------------------------
Ran 22 tests in 3.105s

FAILED (errors=4)

@mottosso
Copy link
Owner

Do you have any idea why that is – and, do you see this on your end as well?

I remember you asking this elsewhere, and I was about to answer it there but couldn't find it. :S

Yes, I've got an idea, it's because for a QApplication to be instantiated, there needs to be an X-server running and we haven't started one in our Docker image.

On Travis, one is being setup here. As Travis doesn't have access to a graphics card or display, this one is a virtual display, called Xvfb. We should be able to use this in Docker too.

@fredrikaverpil
Copy link
Collaborator Author

Ah, I've been searching and I did find references to xvfb.

Do you think that's the best way forward?
I know it's possible (not how though) to run GUIs in Docker containers, somehow mapping the display. Perhaps that's too cumbersome and difficult and not cross platform?

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Jul 31, 2016

Need to somehow suppress all that stuff Xvfb spews out.

This is from my OS X desktop, running Docker for Mac 1.12.0-rc-beta20:
https://gist.github.com/fredrikaverpil/4b339cb5bc3c7d803609d79c4970c99c#file-osx_desktop_tests_log-txt

There's also a strange error appearing on my OS X laptop (running Docker for Mac 1.12 stable):
https://gist.github.com/fredrikaverpil/4b339cb5bc3c7d803609d79c4970c99c#file-osx_laptop_tests_log-txt

======================================================================
ERROR: Failure: RuntimeError (Unable to open/read ui device)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 133, in run
    self.runTest(result)
  File "/usr/local/lib/python2.7/dist-packages/nose/case.py", line 151, in runTest
    test(result)
  File "/usr/local/lib/python2.7/dist-packages/nosepipe.py", line 179, in __call__
    (header + data).decode("latin1"))
Exception: short message body (want 1147499369, got 2165)
Something went wrong
Message: Designer: An error has occurred while reading the UI file at line 1, column 0: Premature end of document.
    startTest\xf2addError:(cexceptions
RuntimeError

@mottosso
Copy link
Owner

An error has occurred while reading the UI file at line 1, column 0: Premature end of document.

I got this as well, I replaced the .ui contents with this instead, that I got from an empty Qt Designer with a single pushbutton, and then never saw the error again. Not entirely sure it was the cause, possibly the lacking newline at the end, possibly the encoding or xml version. Haven't had the opportunity to investigate much further.

    source = """\
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <widget class="QPushButton" name="pushButton">
   <property name="geometry">
    <rect>
     <x>140</x>
     <y>120</y>
     <width>90</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>PushButton</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>
"""

@mottosso
Copy link
Owner

Do you think that's the best way forward?

Yes, without a graphics card or physical display we'll need to go virtual.

I know it's possible (not how though) to run GUIs in Docker containers

You can run GUIs with Xvfb too, they will simply be rendered to an in-memory location (that you could potentially render to a file or redirect to a display).

To redirect GUIs directly, we'll need an X-server to redirect to, or something like an X-server emulator and VNC service like noVNC. In a desktop environment, that's possible. But it's less automatic and headless, which is what we need for tests.

@fredrikaverpil
Copy link
Collaborator Author

Ok, let's go with Xvfb then. 👍

@@ -7,21 +7,16 @@ RUN apt-get update && apt-get install -y \
xvfb

# Nose is the Python test-runner
RUN pip install nose nosepipe
RUN pip install nose nosepipe xvfbwrapper
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About this, have a look at xvfb-run. It does the same thing, but without infecting our tests. This might limit tests from running on anything but Linux, and adds that additional indentation to a lot of lines.

$ xvfb-run docker run ...

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Jul 31, 2016

Tests with docker seems to run smoothly now:

$ docker run --rm -v $(pwd):/Qt.py mottosso/qt.py

Doctest: test_caveats.test_1_qtgui_qabstractitemmodel_createindex ... ok
Doctest: test_caveats.test_2_qtgui_qabstractitemmodel_createindex ... ok
Doctest: test_caveats.test_3_qtcore_qitemselection ... ok
Doctest: test_caveats.test_4_qtcore_qitemselection ... ok
Doctest: test_caveats.test_5_qtcore_qitemselection ... ok
Doctest: test_caveats.test_6_qtcore_qitemselection ... ok
Doctest: test_caveats.test_7_qtcore_slot ... ok
Doctest: test_caveats.test_8_qtcore_slot ... ok
Doctest: test_caveats.test_9_qtwidgets_qaction_triggered ... ok
Doctest: test_caveats.test_10_qtwidgets_qaction_triggered ... ok
Tests require PySide and PyQt4 bindings to be installed ... ok
Setting QT_PREFERRED_BINDING properly forces a particular binding ... ok
Setting QT_PREFERRED_BINDING to more than one binding excludes others ... ok
Preferring None shouldn't import anything ... ok
Qt.py may be use alongside the actual binding ... ok
Preferred binding PyQt4 should have sip version 2 ... ok
Qt.py may be bundled along with another library/project ... ok
load_ui: Load widgets into self using PySide ... ok
load_ui: Load widgets into self using PyQt4 ... ok
load_ui: Load .ui file into custom using PySide ... ok
load_ui: Load widgets into custom using PyQt4 ... ok
Raise ImportError if sip API v1 was already set (Python 2.x only) ... ok

----------------------------------------------------------------------
Ran 22 tests in 4.415s

OK

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Jul 31, 2016

xvfb-run docker run ...

(Referring to this line note)

Hm, I'm not too keen on that since it'll make it impossible to run that on OS X which I like to use.

@fredrikaverpil
Copy link
Collaborator Author

For kicks, here's another one that might work.

Nice! Added a variation of that as a test.

I think you can safely ignore members that startswith("_"), or perhaps more safely startswith("__").

I performed a few local tests and yes, I'm now skipping anything with double leading underscores.

I also cleaned up the descriptions in tests.py as well as adjusted the README accordingly.

@mottosso
Copy link
Owner

I think we're making great strides here.

Based on the new tests, and the fact that this simplification of manually augmenting another class with a ui file works, I've got a suggestion on how to boil it all down into a minimal addition to the project.

How about we leave the additional arguments out, and make these the official methods for working with ui-files and Qt.py?

Method 1

import Qt

widget = Qt.load_ui("my.ui")
widget.show()

Method 2

Class = type(setup_ui())

class MyWidget(Class):
  pass

widget = MyWidget()
widget.show()

If it so happens that this covers all use cases, then we'd be adding one hell of a feature, at the cost of an ultra-minimal amount of code (i.e. 0 lines) and a tiny amount of documentation.

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Aug 12, 2016

I love your suggestion, but I don't understand how that is achievable you mean to achieve that just by looking at your examples.

Your first example is what we have today. But I don't understand your second example. The test I added which uses setup_ui assumes our load_ui function accepts the second base_instance argument and if so does its setattr/getattr thing.

What I wish to achieve with the base instance argument in load_ui is I want to somehow specify into which already existing widget the UI should be loaded. Here's a working example (which requires this PR to work):

import sys
from Qt import QtWidgets, load_ui

def setup_ui(base_instance=None):
    # assumes this PR is accepted as-is right now
    return load_ui('ui.ui', base_instance)

app = QtWidgets.QApplication(sys.argv)
Class = type(setup_ui())

class MyWidget(Class):
    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)
        setup_ui(self)

widget = MyWidget()
widget.show()
app.exec_()

If we were to add 0 lines of code and skip this PR you'd need to do something like this ... working example:

import sys
from Qt import QtWidgets, load_ui

def setup_ui(base_instance=None):
    ui = load_ui('ui.ui')
    if not base_instance:
        return ui
    else:
        for member in dir(ui):
            if not member.startswith('__') and member is not 'staticMetaObject':
                setattr(base_instance, member, getattr(ui, member))
        return ui


app = QtWidgets.QApplication(sys.argv)
Class = type(setup_ui())

class MyWidget(Class):
    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)
        setup_ui(self)


widget = MyWidget()
widget.show()
app.exec_()

Perhaps it is time to decide whether or not we should at all provide a this second argument to load_ui, since it seems we must then be able to test for it and maintain it in case it turns out not being stable.

I'm all for presenting the above example code instead of accepting this PR, don't get me wrong. But I believe we've reached a point where if we can't collectively decide on which of all the different routes which have been presented so far the end-user is better off deciding themselves on which route to take. If this is the case, I'd like to propose we create an "examples" folder or something, where we can document usage and where anyone can contribute their hacks which utilises Qt.py. For instance, we could add all of the proposed solutions on how to load the .ui in PySide to mimic the behavior in PyQt4. We can add comments and valuable lessons learned etc. This would enable usage and guidance without requiring maintainability on our side (and make it easier to turn down such PRs in the future, directing such PRs to the "examples" instead).

@mottosso
Copy link
Owner

mottosso commented Aug 12, 2016

Ok, looks like we've caught a few different understanding of things, that's a good thing.

This is the part I'm having trouble with.

Class = type(setup_ui())

class MyWidget(Class):
    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)
        setup_ui(self)

If you inherit from the GUI built from the ui file, then there shouldn't be any need to run setup_ui(self). It should already have inherited everything.

What does setup_ui(self) do, that isn't already done via inheritance?

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Aug 12, 2016

Class = type(setup_ui())

class MyWidget(Class):
    pass

is the same as

class MyWidget(QtWidget.QMainWindow):
    pass

...and therefore it doesn't set up the ui from the file.

I added the setup_ui function to the test which takes a base instance argument. It performs the setattr/getattr to the given base instance.

This means that you could do this to also set up the ui:

def setup_ui(base_instance=None):
    return load_ui('ui.ui', base_instance)

Class = type(setup_ui())

class MyWidget(Class):
    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)
        setup_ui(base_instance=self)

But all of this would require this PR to be accepted for that to work, as the setup_ui function in my test calls load_ui which accepts the base_instance argument. This was in my test:

def setup_ui(base_instance=None):
    return load_ui(sys.modules[__name__].ui_qmainwindow, base_instance)

However, if the end-user provided their own setup_ui function themselves, this PR doesn't need to be accepted. Working example:

def setup_ui(base_instance=None):
    ui = load_ui('ui.ui')
    if not base_instance:
        return ui
    else:
        for member in dir(ui):
            if not member.startswith('__') and member is not 'staticMetaObject':
                setattr(base_instance, member, getattr(ui, member))
        return ui

@fredrikaverpil
Copy link
Collaborator Author

Sorry for all the editing of my previous post. I'm done now. :)

@mottosso
Copy link
Owner

is the same as

Aha! This is what I didn't understand. I would have thought that the instance returned by load_ui would be an instance of the class generated from the ui file.

E.g.

def load_ui():
  class MyCustomUi(QMainWindow):
    pass

  return MyCustomUi()

And in that way, getting the class from the instance would retrieve the original, that still contained the ui; i.e. MyCustomUi.

The fact that it instead returns an instance of QMainWindow is utterly strange. :S

@fredrikaverpil
Copy link
Collaborator Author

The fact that it instead returns an instance of QMainWindow is utterly strange. :S

Yeah, it's a bit odd. I agree 😕

@fredrikaverpil
Copy link
Collaborator Author

But anyway, even if that would have worked... that approach wouldn't had worked if you wanted to load your ui into something else which already existed.

@mottosso
Copy link
Owner

that approach wouldn't had worked if you wanted to load your ui into something else which already existed.

That sounds backwards to me. Are you building off of a ui file, or is the ui file building off of your Python class? Treating the ui file as a class from which to inherit sounds at least reasonable, but again I haven't used this feature so I don't know.

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Aug 12, 2016

Today the functionality overwrites any existing widget:

>>> widget = QtWidgets.QWidget()
>>> widget = load_ui(fname)
>>> print(type(widget))
<class 'PyQt5.QtWidgets.QMainWindow'>

The second argument is intended to add the ui elements to the already existing widget. So when you provide self, the ui elements are added to it without overwriting self completely.

However, I'm not 100% sure of where things are being overwritten and where they are not. So perhaps my code snippet is a bit misleading on what I'm trying to communicate...

Are you building off of a ui file, or is the ui file building off of your Python class?

Both, I guess.

Consider this... you may be writing an UI which also uses .ui files as "modules" within that UI. So, okay, this is not the best example, but bare (bear) with me...

You're making some kind of image viewer in Qt Designer to show the overall interface. So you base your main app/UI against the .ui file. Then you would perhaps want to code a widget to represent each instance of the actual image thumbnail. Ok. Let's call each such instance a "module".

Now, what if you didn't make a simple image viewer, but some other kind of viewer which would show more complex "modules". I've resorted to using Qt Designer to design such modules. This means I use an .ui for the main interface but then I also load .ui files which are not supposed to serve the main interface so to speak.

Perhaps this was overly explanatory and perhaps not answering what you were asking...?

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Aug 12, 2016

The thing to focus on, is really that when you load an .ui you often want to control into which location the UI's widgets end up.

This is the main reason why I started this PR.

Right now (master branch) you get new widgets everytime you load an .ui. With a base_instance argument, you'd be able to add your .ui file's contents to an already existing widget.

So, instead of calling new_widget.lineEdit you could call self.lineEdit.

@mottosso
Copy link
Owner

Ok, I've thought about it, and I don't think I'm not in a great position to make this call. I think what will need to happen is that we finish implementing the tests such that known corner cases are at least documented and tested, and then you'll decide whether we add it or not, @fredrikaverpil.

This can go 1 of 3 ways.

  1. We add the additional argument to Qt.py
  2. We move load_ui to it's own, standalone and optional module, uic.py
  3. We don't add the argument

Here's what I think so far.

load_ui truly does add unique value not otherwise achievable without great effort. It also adds something unique to the project on a technical level, in that it adds actual implementation, rather than simply rewiring things. We'll need to decide whether that's a direction we want to take, because there might be more, and it might overtake the otherwise simple nature of Qt.py. On the upside, it makes it more useful as well. Separating it into its own module would make it more challenging to install, but at the same time insulate it from bloat for those who aren't using it.

What I don't like about it is simply how this is used. The fusing of an automatically generated GUI with something written by hand. But, having read up on it, this is how the Qt developers intended, so it's only fair we do the same.

I would however like to offer one final challenge to the additional argument before fully embracing it.

You mention using one ui file to augment your subclass, and then using minor ones as standalone additions to it.

What is the below example missing.

class Gui(object):
  def __init__(self):
    ui = load_ui("main.ui")

    layout = ui.body.layout()
    for page in ("page1.ui", "page2.ui"):
      widget = load_ui(page)
      layout.addWidget(page)

    # Store reference
    self.ui = ui

  def show(self):
    self.ui.show()

gui = Gui()
gui.show()

Let me know if you need a full runnable example, or whether this communicates the point fully.

And have a think about whether a dedicated uic.py module is feasible. It's possible that could solve the issue of Qt.py remaining simple and solid, whilst still enabling additional development happen on the side and remain optional to those that do not need it.

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Aug 15, 2016

The fusing of an automatically generated GUI with something written by hand.

Yes, I believe that was the design intent. At least that's how it's being used by end-users.

What is the below example missing.

In your example, you first load the main UI into self.ui, which makes all of its widgets reside in (and become accessible from) self.ui. What if you wanted the rest of the widgets from page1.ui and page2.ui to be accessible from self.ui?

I think that's what's missing, with our discussions as context.

However, I don't personally think there's anything wrong with your code example and I'm not on any kind of crusade here. I think your comments on this is sound and makes sense. I'm rewriting a lot of code at the moment using Qt.load_ui without this second base argument and I'm doing just fine. It's just that it seems people are using that second argument and would like to keep using it.

This can go 1 of 3 ways.

Let me propose a fourth way :) (which I actually did propose in some other issue or PR, can't remember)

How about we store testable examples of how to use the current functionality of load_ui (meaning no second argument added) as well as more advanced usage (such as loading widgets into a base instance). This would almost mean the same thing as maintaining uic.py but without relying on having to maintain it. This could also open up for other "examples", where we feel a pull request is more suited as an example rather than an actual implementation in Qt.py.

I think back on #101 and what you said there...

I would much rather see software developed with cross-compatibility in mind, than software relying on a shim for missing functionality.

I feel load_ui today is just offering cross-compatibility and not much more. Which is great, because it provides a lot of aid when loading a .ui file. It does what Qt.py set out to do. So, adding this layer of reliability on load_ui I must say troubles me somewhat as well.

So, I'd say we have four options:

  • We add the additional argument to Qt.py
  • We move load_ui to it's own, standalone and optional module, uic.py
  • We don't add the argument
  • We don't add the argument, but provide a facility for advanced usage of it

@mottosso
Copy link
Owner

That approach feels comfortable to me.

It means we won't have to make too many contraptions or add to much opinion to the codebase, but instead leave that for suggestions via examples and best practices. It will leave more code for the end-user. At first. But potentially save code long-term, considering obscurities and subtle differences unaccounted for or worked around in our codebase that would result in additional in code that way.

I'd say it'd be a defining direction for the project to take. It will not sit well with everyone. Some may choose to build something in parallel to better suit their needs and that project may very well overshadow ours. Or not. Either way, I think it might ultimately help guide the way, if only to find which route not to take.

So, reducing the four technical options into two, we're left with these high-level options.

  1. Wrap bindings to provide a single, unified interface across all bindings.
  2. Remap similarities and offer advice and best practice on how to develop software which works across bindings.

If possible, I'd like to hear from @bpabel and @hdd before dropping the hammer.

@fredrikaverpil
Copy link
Collaborator Author

If possible, I'd like to hear from @bpabel and @hdd before dropping the hammer.

I'd like that too. Ping...

@fredrikaverpil
Copy link
Collaborator Author

fredrikaverpil commented Aug 16, 2016

Remap similarities and offer advice and best practice on how to develop software which works across bindings.

Having given this some thought, I'm personally leaning towards this. It's also in line with #101

@hdd
Copy link
Contributor

hdd commented Aug 22, 2016

Hi , sorry for the delay on joining the chat , just come back from a quick break.

To me , and how I did started approaching the patches, the best way to go, is to start with the new api in mind (so, relying on QtWidgets), and provide patched methods to be able to run the legacy code (PySide/PyQt) (eg: #98 ).

This allow the users to start using , and getting used to the new api, but having the ability to have their code working with not much to change.

We should, though, warn the users, that they are using these patched methods (whether are forward or backward compatibility), so they are aware.

My2c.
L.

@mottosso
Copy link
Owner

We should, though, warn the users, that they are using these patched methods (whether are forward or backward compatibility), so they are aware.

Could you open an issue about this? I see a number of conversation topics on how to achieve this technically.

@hdd
Copy link
Contributor

hdd commented Aug 22, 2016

done, see #109

@fredrikaverpil fredrikaverpil mentioned this pull request Aug 26, 2016
@mottosso
Copy link
Owner

Ok, let's commit to the goals discussed in this issue!

Henceforth, Qt.py is about linking together commonalities between PySide2 and other bindings, and documenting differences, so as to foster a mindful developer of cross-compatible software.

This is in contrast to wrapping all functionality that differs PySide2 with our own ad-hoc implementations, which we believe will cause more problems than it solves long-term.

For alternative ideals, see the following forks and projects.

Any more? Let us know!

@mottosso mottosso closed this Aug 26, 2016
@fredrikaverpil fredrikaverpil mentioned this pull request Aug 29, 2016
@fredrikaverpil
Copy link
Collaborator Author

For anyone reading this enormous thread, check out this pull request: #119

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants