How to inherit from GObject class? - python

I want to write application with a tree view widget in witch I will store my objects of class 'Item'.
I know that in order to do so my 'Item' class must inherit from GObject class. Unfortunately something is wrong and I don't see text of items on my tree. I only get this warning:
Warning: unable to set property 'text' of type 'gchararray' from value of type '__main__+Item'
What I will have to do to fix this?
This sample program is demonstrates problem and it's ready to test fixes:
#!/usr/bin/env python3
from gi.repository import Gtk
from gi.repository import GObject
class Item(GObject.GObject):
text = GObject.property(type=str, default='item', flags=GObject.PARAM_READWRITE)
def __init__(self, title):
GObject.GObject.__init__(self)
self.__title = title
def __str__(self):
return self.__title
GObject.type_register(Item)
class MainWindow(Gtk.Window):
def __init__(self):
super().__init__(Gtk.WindowType.TOPLEVEL)
self.connect('destroy', self.on_destroy)
tree_model = Gtk.TreeStore(Item.__gtype__)
# tree_model = Gtk.TreeStore(str)
text_renderer = Gtk.CellRendererText()
text_column = Gtk.TreeViewColumn(None, text_renderer)
text_column.add_attribute(text_renderer, 'text', 0)
tree_view = Gtk.TreeView(tree_model)
tree_view.append_column(text_column)
self.add(tree_view)
self.show_all()
tree_model.append(None, (Item('test'),))
# tree_model.append(None, ('It works!',))
def on_destroy(self, e):
Gtk.main_quit()
if __name__ == '__main__':
MainWindow()
Gtk.main()

GtkCellRendererText requires string (gchararray) data for its text property, and it is receiving custom GObject values. The __str__ function works on the Python level and is never invoked by GObject.
Fortunately, what you want to achieve doesn't require subclassing GObject. You need to do the following:
Specify the tree store column as GObject.TYPE_PYOBJECT. This will allow you to append your instances to the tree store without inheritance from GObject or special properties.
Use set_cell_data_func on the tree view columns to extract textual data from your instances stored in the model.
See this answer for a working example of this technique.

Related

Unable to overwrite class method in pyqt6 application

So normally you can overwrite a class method by doing something like this.
class A():
def test(self):
return 1+1
def edit_patch(func):
def inner(*args,**kwargs):
print('this test worked')
return inner
a=A()
a.test = edit_patch(a.test)
Now a.test will return 'this test worked' instead of 2. I'm trying to do something similar in my pyqt6 application. The function below belongs to the "main" class in my code and is connected to a button click. This function is meant to instantiate another class (which is another window in pyqt6). That part works, but I would like to alter the behavior of the select function in this instance. However the method above doesn't seem to work as the select function continues to exhibit the default behavior.
def edit_proj(self):
self.psearch=PSearch(conn=self.conn,parent=self)
self.psearch.select = edit_patch(self.psearch.select)
self.psearch.show()
Any help on this would be great
As requested, here is an MRE
from PyQt6 import QtCore, QtGui, QtWidgets
def edit_patch(func):
def inner(*args,**kwargs):
print('this test worked')
return inner
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(50, 50)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.EditProjButton = QtWidgets.QPushButton(self.centralwidget)
self.EditProjButton.setObjectName("EditProjButton")
self.EditProjButton.clicked.connect(self.nextwindow)
def nextwindow(self):
print('hello from main window')
self.newwindow=Ui_ProjSearchForm(QtWidgets.QWidget())
self.newwindow.select = edit_patch(self.newwindow.select)
self.newwindow.show()
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(self.select)
def setupUi(self, ProjSearchForm):
ProjSearchForm.setObjectName("ProjSearchForm")
ProjSearchForm.resize(100, 100)
self.gridLayout = QtWidgets.QGridLayout(ProjSearchForm)
self.gridLayout.setObjectName("gridLayout")
self.SearchButton = QtWidgets.QPushButton(ProjSearchForm)
self.SearchButton.setObjectName("SearchButton")
self.gridLayout.addWidget(self.SearchButton, 0, 2, 1, 1)
def select(self):
print('this is default behavior')
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())
Signal connections work by passing a reference to a callable, and that reference is an "internal" pointer to that function. Overwriting the name of that function will have absolutely no result.
Take this example:
class Test(QPushButton):
def __init__(self):
super().__init__('Click me!')
self.clicked.connect(self.doSomething)
self.doSomething = lambda: print('bye!')
def doSomething(self):
print('hello!')
The code above will always print "hello!", because you passed the reference to the instance method doSomething that existed at the time of the connection; overwriting it will not change the result.
If you need to create a connection that can be overwritten, you have different possibilities.
Pass the function to the constructor
You can set the function as an optional argument in the __init__ and then connect it if specified, otherwise use the default behavior:
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm(edit_patch(self.newwindow.select))
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
if func is not None:
self.SearchButton.clicked.connect(func)
else:
self.SearchButton.clicked.connect(self.select)
Create a method for the connection
In this case we pass the reference to a specific method that will create the connection, eventually disconnecting any previous connection (remember that signals can be connected to multiple functions, and even the same function multiple times). This is similar to the approach above.
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm()
self.newwindow.setSelectFunc(edit_patch(self.newwindow.select))
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(self.select)
def select(self):
print('this is default behavior')
def setSelectFunc(self, func):
try:
self.SearchButton.clicked.disconnect(self.select)
except TypeError:
pass
self.select = func
self.SearchButton.clicked.connect(self.select)
Use a lambda
As said above, the problem was in trying to overwrite the function that was connected to the signal: even if the connected function is a wrapper, the direct reference for the connection is not actually overwritten.
If you, instead, connect to a lambda that finally calls the instance method, it will work as expected, because the lambda is dynamically computed at the time of its execution and at that time self.select will be a reference to the overwritten function.
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm()
self.newwindow.select = edit_patch(self.newwindow.select)
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(lambda: self.select())
def select(self):
print('this is default behavior')
Some unrelated but still important notes:
You should never edit pyuic generated files, nor try to merge their code into your script or mimic their behavior. Instead, follow the official guidelines about using Designer.
Passing a new QWidget instance as argument is pointless (other than wrong and potentially dangerous); if you want to create a new window for the new widget, just avoid any parent at all, otherwise use QDialog for modal windows.
Only classes and constants should have capitalized names, everything else should be named starting with lowercase letters (this includes object names created in Designer); read more about this and other important topics in the official Style Guide for Python Code.
Ok I think I've figured out a solution (in the MRE posted in question). There's some shenanigans that go on in the back ground once you connect a button in the UI to a function. It's not a "live" connection like in the a.test example, so edits to the function later don't have an impact on how the button functions.
So, if we replace
self.newwindow.select = edit_patch(self.newwindow.select)
with
self.newwindow.SearchButton.clicked.disconnect()
self.newwindow.select = edit_patch(self.newwindow.select)
self.newwindow.SearchButton.clicked.connect(self.newwindow.select)
we suddenly get the desired behavoir from the button. This was entirely too frustrating.

How to get a variable from a different class?

I have a class named TerminalPanel which has the following method:
def OnSerialRead(self, event):
"""Handle input from the serial port."""
text = event.data
Now, I want to get the value of text from another method (get_data) in another class (GraphicsPanel).
How do I get this value? I tried marito = TerminalPanel.OnserialRead.text, but I get AttributeError: 'function' object has no attribute 'text'
Update
I have set-up the TerminalPanel class to include the variable text as part of it:
def OnSerialRead(self, event):
"""Handle input from the serial port."""
self.text = event.data
But now when I call it like this: marito = TerminalPanel.text inside my GraphicsPanel class I get the following error:
AttributeError: type object 'TerminalPanel' has no attribute 'text'
What am I doing wrong?
I think the problem is a lack of context and confusion what actually to do. I suppose you try to rework the wxTerminal.py from pyserial. I have to admit this part of pyserial is neither very readable (has been created by wxGlade) nor is it easy to understand (requires understanding of the wxPython event system and spinning off of threads (to keep the GUI responsive when reading on the serial port).
However, according to your problem description, it seems to me you want to do the following:
Get the value of event.text when it arrives and process it further in your GraphicsPanel instance.
You have to possibilities:
1) Bind to the event:
In your GraphicsPanel class:
class GraphicsPanel(wx.Panel):
def __init__(...):
...
self.parent = self.GetParent() # this should result in a wx.Frame instance!
# binding on wx.Frame required, because wx.Panel will not catch the event
self.parent.Bind(EVT_SERIALRX, self.OnSerialRead)
def OnSerialRead(self, event):
text = event.text
...
event.Skip() # important: you have to skip it also in ``TerminalPanel`` if you
# want to bind twice
2) Call the routine in GraphicsPanel instance with event.text as argument.
class TerminalPanel(wx.Panel):
def __init__(...):
...
self._grphpnl = GraphicsPanel(...)
self.Bind(EVT_SERIALRX, self.OnSerialRead)
def OnSerialRead(self, event):
text = event.text
# do something with it in GraphicsPanel instance
self._grphpnl.OnSerialText(text)
...
Somewhere else in your code:
class GraphicsPanel(wx.Panel):
...
def OnSerialText(text):
# do something with the text
That variable is defined at function scope. There is no way to get that value.
To make the value available to anything outside of the method you need to store the value on the class self.text = event.data or return the value return text
You need to decide what is right for the situation though, I'm guessing by the name of the function that returning the data is the right thing to do.
You need to return the value!
def OnSerialRead(self, event):
"""Handle input from the serial port."""
text = event.data
return text
Then you can access the value like this
marito = TerminalPanel.OnserialRead(event)
Or save the value in the class:
class Reader():
def OnSerialRead(...):
...
self.text = event.data
and then access the value from the class like so:
marito = Reader.text

Understanding a piece of python code

My question refers to the accepted answer of the question How to capture output of Python's interpreter and show in a Text widget? which shows how to redirect standard output to a QTextEdit.
The author, Ferdinand Beyer, defines a class EmittingStream as such:
from PyQt4 import QtCore
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
He uses the class like this:
# Within your main window class...
def __init__(self, parent=None, **kwargs):
# ...
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
def __del__(self):
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
cursor = self.textEdit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.textEdit.setTextCursor(cursor)
self.textEdit.ensureCursorVisible()
I don't understand the line that instantiates the EmittingStream class. It looks as if the keyword argument textWritten=self.normalOutputWritten connects the textWritten-signal to the normalOutputWritten-slot, but I do not understand why this works.
This feature is documented here:
It is also possible to connect signals by passing a slot as a keyword
argument corresponding to the name of the signal when creating an
object, or using the pyqtConfigure() method of QObject. For example
the following three fragments are equivalent:
act = QtGui.QAction("Action", self)
act.triggered.connect(self.on_triggered)
act = QtGui.QAction("Action", self, triggered=self.on_triggered)
act = QtGui.QAction("Action", self)
act.pyqtConfigure(triggered=self.on_triggered)

api: how to get selected text from object sublime.Selection

How to get selected text in sublime text 3 plugin:
import sublime, sublime_plugin
class plugin_window__go_to_relative_plugin__Command(sublime_plugin.WindowCommand):
def run(self):
window = self.window
view = window.active_view()
sel = view.sel()
sublime.status_message("selection: "+sel)
My code throws error:
sublime.status_message("selection: "+sel)
TypeError: Can't convert 'Selection' object to str implicitly
view.sel() returns sublime.Selection object. But I don't know how to get selected text from there.
This plugin must work as following:
When I call it on view...
... it should set text "dow = self.w" to variable sel
When I do str(sel) it returns <sublime.Selection object at 0x1047fd8d0>
Docs are not very clear for me.
My understanding of what the documentation means is this:
It sounds like the sel() method of a sublime.View object returns a sublime.Selection object, which is a container of regions—so you should be able to iterate over its contents (the regions it contains) or index into them using the [] operation.
You can get the text associated with each sublime.Region in a Selectionby calling the substr(region) method of a sublime.View object. This makes sense as this editor allows there to be multiple simultaneous selections—one of its cooler features, IMHO.
Hope this helps.
In case of single selection:
import sublime, sublime_plugin
class selection_plugin__Command(sublime_plugin.WindowCommand):
def run(self):
print('selection_plugin__ called')
window = self.window
view = window.active_view()
sel = view.sel()
region1 = sel[0]
selectionText = view.substr(region1)
print(selectionText)
Use it in console and then append in python
view.substr((view.sel())[0])

pygtk gtk.Builder.connect_signals onto multiple objects?

I am updating some code from using libglade to GtkBuilder, which is supposed to be the way of the future.
With gtk.glade, you could call glade_xml.signal_autoconnect(...) repeatedly to connect signals onto objects of different classes corresponding to different windows in the program. However Builder.connect_signals seems to work only once, and (therefore) to give warnings about any handlers that aren't defined in the first class that's passed in.
I realize I can connect them manually but this seems a bit laborious. (Or for that matter I could use some getattr hackery to let it connect them through a proxy to all the objects...)
Is it a bug there's no function to hook up handlers across multiple objects? Or am I missing something?
Someone else has a similar problem http://www.gtkforums.com/about1514.html which I assume means this can't be done.
Here's what I currently have. Feel free to use it, or to suggest something better:
class HandlerFinder(object):
"""Searches for handler implementations across multiple objects.
"""
# See <http://stackoverflow.com/questions/4637792> for why this is
# necessary.
def __init__(self, backing_objects):
self.backing_objects = backing_objects
def __getattr__(self, name):
for o in self.backing_objects:
if hasattr(o, name):
return getattr(o, name)
else:
raise AttributeError("%r not found on any of %r"
% (name, self.backing_objects))
I have been looking for a solution to this for some time and found that it can be done by passing a dict of all the handlers to connect_signals.
The inspect module can extract methods using
inspect.getmembers(instance, predicate=inspect.ismethod
These can then be concatenated into a dictionary using d.update(d3), watching out for duplicate functions such as on_delete.
Example code:
import inspect
...
handlers = {}
for c in [win2, win3, win4, self]: # self is the main window
methods = inspect.getmembers(c, predicate=inspect.ismethod)
handlers.update(methods)
builder.connect_signals(handlers)
This will not pick up alias method names declared using #alias. For an example of how to do that, see the code for Builder.py, at def dict_from_callback_obj.
I'm only a novice but this is what I do, maybe it can inspire;-)
I instantiate the major components from a 'control' and pass the builder object so that the instantiated object can make use of any of the builder objects (mainwindow in example) or add to the builder (aboutDialog example). I also pass a dictionary (dic) where each component adds "signals" to it.
Then the 'connect_signals(dic)' is executed.
Of course I need to do some manual signal connecting when I need to pass user arguments to the callback method, but those are few.
#modules.control.py
class Control:
def __init__(self):
# Load the builder obj
guibuilder = gtk.Builder()
guibuilder.add_from_file("gui/mainwindow.ui")
# Create a dictionnary to store signal from loaded components
dic = {}
# Instanciate the components...
aboutdialog = modules.aboutdialog.AboutDialog(guibuilder, dic)
mainwin = modules.mainwindow.MainWindow(guibuilder, dic, self)
...
guibuilder.connect_signals(dic)
del dic
#modules/aboutdialog.py
class AboutDialog:
def __init__(self, builder, dic):
dic["on_OpenAboutWindow_activate"] = self.on_OpenAboutWindow_activate
self.builder = builder
def on_OpenAboutWindow_activate(self, menu_item):
self.builder.add_from_file("gui/aboutdialog.ui")
self.aboutdialog = self.builder.get_object("aboutdialog")
self.aboutdialog.run()
self.aboutdialog.destroy()
#modules/mainwindow.py
class MainWindow:
def __init__(self, builder, dic, controller):
self.control = controller
# get gui xml and/or signals
dic["on_file_new_activate"] = self.control.newFile
dic["on_file_open_activate"] = self.control.openFile
dic["on_file_save_activate"] = self.control.saveFile
dic["on_file_close_activate"] = self.control.closeFile
...
# get needed gui objects
self.mainWindow = builder.get_object("mainWindow")
...
Edit: alternative to auto attach signals to callbacks:
Untested code
def start_element(name, attrs):
if name == "signal":
if attrs["handler"]:
handler = attrs["handler"]
#Insert code to verify if handler is part of the collection
#we want.
self.handlerList.append(handler)
def extractSignals(uiFile)
import xml.parsers.expat
p = xml.parsers.expat.ParserCreate()
p.StartElementHandler = self.start_element
p.ParseFile(uiFile)
self.handlerList = []
extractSignals(uiFile)
for handler in handlerList:
dic[handler] = eval(''. join(["self.", handler, "_cb"]))
builder.connect_signals
({
"on_window_destroy" : gtk.main_quit,
"on_buttonQuit_clicked" : gtk.main_quit
})

Categories

Resources