Issue while trying to copy pyside object - python

I am having a rather frustrating problem using pyside and I would welcome any advice.
First, some context
I have created a simple GUI using Qt Designer and I have used pyside-uic.exe onto my .ui file in order to generate the associated Python file.
I am using Python 3.3 and pyside 1.2.1 with Qt Designer 4 (Qt 4.8.5).
I am using the following code to launch my GUI:
class my_dialog(QMainWindow, my_gui.Ui_main_window):
def __init__(self, parent=None):
super(my_dialog, self).__init__(parent)
self.setupUi(self)
if ("__main__" == name):
app = QApplication(sys.argv)
main_dialog = my_dialog()
# (1)
main_dialog.show()
sys.exit(app.exec_())
What I would like to achieve
My GUI features several tabs. The number of tabs is not pre-determined and is evaluated at run time. As a result, I've decided to create one tab in Qt Designer, to use as a template.
The first time I need to add a tab, I modify this template, and if I need any additionnal tab, I was planning on making a copy of that tab and then modify that copy appropriately.
The issue I have encountered
My problem is that I can't seem to find a way to copy the tab widget. After some research, I thought the copy module (or the pickle module, see edit) might do the trick (the following code was inserted at (1)):
new_tab = copy.deepcopy(main_dialog.my_tab)
main_dialog.my_tabs.addTab(new_tab, "")
But that triggered the following error:
main_dialog.my_tabs.addTab(new_tab, "")
RuntimeError: Internal C++ object (Pyside.QtGui.QWidget) already deleted
What I could find on my own
I have seen on SO and other sites that there may be issues, when using pyside, of objects being collected because there is no reference to them in Python.
The fact remains, however, that even if I move this code to very setupUi() method in the .py file generated by pyside, I still get the exact same error.
It is also worth noting that I am able to access the my_tab object to modify its content without any trouble.
I am able to create another tab from scratch in my code and main_dialog.my_tabs.addTab(new_tab, "") works perfectly fine in that context.
After some experimentations, I realized the problem probably occurs at the copy of the my_tab object. Indeed, copying a tab object that I just created, I could see that trying to add the copy to the GUI tabs failed too, and with the same error.
It looks like the copy fails somehow, or the object is being immediately deleted for some reason. That's what I'm infering anyway...
My question
Considering all this, I would like to find a way to either succeed in the object copy, find another way to use an existing pyside object as template for other similar objects.
I could of course take the code for the tab out of the generated file and code my own addTab() method. However, I am expected to build from an existing .ui file and avoid hardcoding GUI elements.
EDIT:
When using pickle:
new_tab = pickle.loads(pickle.dumps(main_dialog.my_tab, -1))
I get the following error:
new_tab = pickle.loads(pickle.dumps(main_dialog.my_tab, -1))
_pickle.PicklingError: Can't pickle <class 'Pyside.QtCore.SignalInstance'>: attribute lookup Pyside.QtCore.SignalInstance failed.

The suggestion of creating a separate ui file for the widget you want to copy seems a reasonable solution. Although it would seem that generating a separate gui module for the widget using pyside-uic would work just as well as using QUiLoader (in fact, it would be slightly more efficient).
As for the question of why cloning widget using e.g. copy.deepcopy doesn't work: this is because it will only copy the python wrapper, and not the underlying C++ object. A somewhat fuller explanation can be found in this answer.

After some more research, I believe copying a pyside object using one of those techniques is not possible.
The first thing to note is that there is no built-in function to clone a Qt widget, so the cloning should be done using modules like copy, pickle or marshal.
Using pickle or marshal fails because the object is found to be not pickable.
Whilst the copy.copy or copy.deeepcopy do not raise any warning/exception/error, the copy does not occur, or is deleted right afterwards for some reason.
When trying to pass in the deepcopy as parameter to addTab, no warning/exception/error is thrown, yet the program stops at that line and exits back to the Python command prompt. The fact that it takes a few seconds on that line before exiting makes me assume deepcopy tries to browse through the object attributes and fails at some point. Doing the same with copy results in the previous C++ object deleted error mentionned in the question, so I can only infer the deepcopy operation does fail.
As a result, the only advice I could give someone looking for a similar answer is to implement their own copy-widget function, which is ultimately what I will do now.
Still, I wish to understand how is that deepcopy fails like this, so silently, yet provoking the end of the execution. I started a thread to try and find an answer to this there
EDIT:
I found a solution for this problem, that respects my requirements of not hard-coding GUI elements and of using Qt Designer to create the GUI and templates for repeatable elements. I hope it helps anyone having the same issue:
The idea is that it is possible using Qt -- and pyside -- to load a given .ui file at run time, using the QUiLoader() method. It is thus possible to parse the .ui file to extract a given widget (.ui files are simple XML files) and use the following code to use it:
loader = QUiLoader()
ui_file = QFile("path_to_ui_file.ui")
ui_file.open(QFile.ReadOnly)
new_tab = loader.load(ui_file)
ui_file.close()
main_dialog.my_tabs.addTab(new_tab, "")
And it works!
A few things about the above example:
the second line assumes you've isolated your widget in the file path_to_ui_file.ui
in my example the widget is a tab, of course it works with any widget you might have done, the last line is only provided to show that the error is no longer thrown
finally, this approach has the pro of allowing you to use tools like Qt Designer to develop your GUI elements even when some variables are involved, such as just how many of that tabs do you want?

Related

Python import from byte IO (or more generally from an object in memory)

Context:
I currently have a program which loads a set of plugins from their file paths (in a mapped network drive) using the method shown in another SO thread. These plugins are designed to be rolling release, which means I need constant access to writing them. The current mechanism locks the files so I have to ask everyone to close the software so I can update the files.
The question:
I was wondering if there was a way to, possibly using a similar method to that linked above, import a file from an io.BytesIO object of the plugin's raw contents (hence unlocking the file for me to make changes as I please).
More generally:
More specifically, can I keep the raw module contents in memory without touching a physical disk? If such a thing is not possible, is there a way to fully load these modules into memory so I can then unlock the files being imported?
As I have stated in my comment, I understand you can mount a virtual-filesystem on a Linux-based OS (which could have solved my problem), though sadly I developing for Windows and Microsoft can never make your life easy! :-)
Note:
I am not asking where I can copy these files to import them from a local version (e.g. temp, cache, etc.).
I understand this is quite a specialist question so any help is much appreciated
While not being from an io.BytesIO object as I originally asked for, I was able to import a module from its source after finding this incredibly helpful article. I have not copied the code here as it is quite large, though I was able to get it to successfully import the virtual module.
The following code is after I modified the loader to remove the common prefix, and creates a class of the module by first executing the source, getting the globals from it and finally using Python's type method to create the module class.
It is not particularly pretty and definitely breaks some Python style recommendations, so I am definitely open to improvements!
source = """def hello():
print("I don't want to say hi to the world")"""
name = "my_module"
glo = {}
exec(source, glo)
injector = DependencyInjector()
injector.provide(name, type(name, (), glo))
injector.install()
foo = __import__(name)
foo.hello()

Could I transform the python code generated from pyuic back to a ui file?

PyQt could tranform Qt ui file to python module through: pyuic5 file.ui > file.py. But could I turn the way the other around? Something like: pyuic5 file.py > file.ui?
I don´t know about a tool that do what you want, but surely you could manually build a ui file by reading the generated py. The generated py file is very descriptive.
If is the case that you lose your file.ui, this is the approach you should follow, don´t lose any time rigth now building such tool. You should focus in getting your project up again.
Another advise, use a version control system for your projects!
There is QAbstractFormBuilder, which can be used to save widgets to a ui file.
That obviously can't convert everything in the python module, but it might allow you to save an instance of the main widget, and perhaps recover most of the original ui file. I say "perhaps", because I have not personaly had much success with it on the few occasions I've tried it - but hopefully others may be luckier.

Callbacks in Python Maya plugin

I am trying to create a callback in my Maya plugin that runs every time a new object is created. The callback works but the data object passed is of NoneType. I want to access the newly created object in my callback function, how do i do that?
g_Callbacks = list()
def initializePlugin( obj ):
g_Callbacks.append( OpenMaya.MEventMessage.addEventCallback( "DagObjectCreated", callback ) )
...
def callback( data ):
print data.apiTypeStr()
From the comments it sounds like there are two separate issues.
Notificiations on object creation are 'free', in the sense that you can do this without the API by using a scriptJob. Here's a trivial example
def new_object_callback():
print "created", cmds.ls(sl=True)
cmds.scriptJob(e=('DagObjectCreated', new_object_callback))
The callback here is just a plain python script , but it could be a function created by an MPXCommand (that link is a good intro to a very simple command plugin, btw).
There is one limitation here: the creation callback will fire once per undo block. A menu item, button or script creates a single undo --- which means that an action which creates multiple objects will only get that notification once (and the example code above will only print out the message for the last created object).
Another way of reading the question is how to fire a callback when you create the object yourself in a plugin. That's a simpler problem, although plugins for creating objects are kind of wordy to write. (A decent intro here -- the python stuff is interleaved with C++, because the docs for all of this are still written for C++) Every plugin node class has to have a function called initialize, which will be called every time the plugin creates a new node, so you can use that to call any code you need to run at startup.
OpenMaya is a real pain in the butt, since you're basically writing C++ through Python. Here's a couple of decent references:
http://www.amazon.com/Maya-Python-Games-Film-Reference/dp/0123785782
http://www.amazon.com/Practical-Programming-Python-Robert-Galanakis/dp/1849694729

wxpython wx.AboutBox single instance

I'm still in the process of learning more about WxPython and I ran into a simple problem.
I like the wx.AboutBox class that manages how credits, licensing and other info is displayed to the user...and I'm not looking forward to building my own from scratch...although I can.
The problem here is that let's say I click on a button which brings up an AboutBox...seems like if you keep on clicking that exact button then multiple instances of the same AboutBox is brought up...and you end up multiple windows that just looks awkward in my opinion. As far as I've looked into it...there's no way to call the ShowModal() function that would allow you to get the ID of the close button in the aboutbox template and do some processing to make sure that only one instance is running.
I want to know how to stop this issue from happening...I want a single instance of wx.AboutBox and if it's not possible with this class due to it's nature/limitations then I'll have to consider building my own as a last resort.
Thanks
Creating your own AboutBox dialog is pretty easy. Here's an article that shows one way to do it: http://www.blog.pythonlibrary.org/2008/06/11/wxpython-creating-an-about-box/ or you could just use a GenericMessageDialog too. See the wxPython demo or http://www.blog.pythonlibrary.org/2010/07/10/the-dialogs-of-wxpython-part-2-of-2/
It seems like you haven't solved this problem yet. In wxPython, there's a class called "SingleInstanceChecker". I think this is what you're looking for.
http://wxpython.org/Phoenix/docs/html/SingleInstanceChecker.html

Gnome-Screensaver with Python?

I've created a little screensaver-type program with Python, and I'd like to get it working properly under gnome-screensaver. The documentation is all written for C, and there don't appear to be libraries for Python. Is there any way to get this working?
gnome-screensaver doesn't integrate with the screensavers any more than with a small config file that sets the name, some other properties, and a command to execute to start the screensaver. This is documented in the gnome-screensaver FAQ.
The program that gets started needs to get the Window ID from the environment (XSCREENSAVER_WINDOW), and start displaying on that window. This is the tricky part, as it's not always obvious how to do that. Most toolkits should have methods to get a handle on an already existing window by giving the ID, so search your GUI-toolkit documentation for something like that.

Categories

Resources