Why does gtk.Table takes twice as much space than it should? - python

I'm using gtk.Table in combination with an EventBox for every cell to draw a colored grid. After trying out a minimal example i've discovered that the window is twice as big as the actual table. Also it's not possible to shrink the window any further when the application is running.
It seems like something went horrible wrong but i'm not able to figure out the cause. Here's the minimal example to reproduce the misbehaviour:
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class MyProgram:
def __init__(self):
app_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
app_window.set_border_width(10)
app_window.connect("delete_event", lambda w,e: gtk.main_quit())
vbox_app = gtk.VBox(False, 0)
table_layout = gtk.Table(rows=1,columns=1, homogeneous=True)
for col in range(1,10):
for row in range(1,5):
event_box = gtk.EventBox()
label_day = gtk.Label("")
label_day.set_size_request(18,18)
label_day.show()
event_box.add(label_day)
event_box.modify_bg(gtk.STATE_NORMAL,
event_box.get_colormap().alloc_color("orange"))
event_box.set_border_width(25)
event_box.show()
table_layout.attach(event_box, 0, col, 0, row, 0,0,0,0)
vbox_app.pack_start(table_layout)
app_window.add(vbox_app)
app_window.show_all()
return
def main():
gtk.main()
return 0
if __name__ == "__main__":
MyProgram()
main()

It turns out it's your event_box.set_border_width(25) that hurts. If you just want to space evenly your labels, us the padding arguments of GtkTable::attach instead.
Here are unrelated improvements:
don't connect gtk_main_quit to the delete-event signal, connect it to the destroy signal instead. delete-event is when you want to do something before quitting (for example, display a popup "are you sure ? yes/no"), but what you want is to quit gtk when the window is really destroyed.
Also, instead of prepending your widgets when adding them in the table, append them and use a range starting from 0 so it's easier to see where the widgets are added (the indexes in the table are zero-based).
As for your VBox, in this context it's useless. If you only have widget which content takes the whole window, just add it directly to your GtkWindow (but maybe it's needed in our unstripped version of the program).
Finally, you don't need to call gtk_widget_show on each widget. Just focus on constructing your widget hierarchy, and then run gtk_widget_show_all on the toplevel window. It will recursively show all the widgets in it.
That gives us in the end:
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class MyProgram:
def __init__(self):
app_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
app_window.set_border_width(10)
app_window.connect('destroy', lambda w: gtk.main_quit())
table = gtk.Table(rows=1, columns=1, homogeneous=True)
for col in range(0,9):
for row in range(0,4):
event_box = gtk.EventBox()
label_day = gtk.Label('')
label_day.set_size_request(18, 18)
event_box.add(label_day)
event_box.modify_bg(gtk.STATE_NORMAL,
event_box.get_colormap().alloc_color("orange"))
# event_box.set_border_width(25)
table.attach(event_box, col, col + 1, row, row + 1, 0, 0, 12, 12)
app_window.add(table)
app_window.show_all()
if __name__ == '__main__':
MyProgram()
gtk.main()
You also have a tool named gtk-inspector but I don't know if it works with GTK 2. If not, fallback on gtk-parasite. These tools will help you analyze a running gtk user interface and see the characteristics of its widgets.
Also, GTK 3 has been out for a few years now and GTK 4 is on the road. Consider using GTK 3 for new code. It doesn't use pygtk anymore, it's pygobject instead trough the gi package (GObject introspection).
Here's the GTK 3 in python official tutorial:
https://python-gtk-3-tutorial.readthedocs.io/en/latest/index.html

Related

Maya Python: Button always at the center of the window

I'm starting experimenting with Maya python, and I'm trying to do some UI.
I came across to a really strange problem, I can't get a button to stay in the center of the windows.
I've tried different things but nothing seems to work, here is the code:
import maya.cmds as cmds
cmds.window( width=200 )
WS = mc.workspaceControl("dockName", retain = False, floating = True,mw=80)
submit_widget = cmds.rowLayout(numberOfColumns=1, p=WS)
cmds.button( label='Submit Job',width=130,align='center', p=submit_widget)
cmds.showWindow()
this is a simple version but still, I can't get it to work.
can someone help me?
I honestly don't know the answer as anytime I have to dig into Maya's native UI stuff it makes me question my own life.
So I know it's not exactly what you're asking for, but I'll opt with this: Use PySide instead. At first glance it might make you go "woah, that's way too hard", but it's also a million times better (and actually easier). It's much more powerful, flexible, has great documentation, and also used outside of Maya (so actually useful to learn). Maya's own interface uses the same framework, so you can even edit it with PySide once you're more comfortable with it.
Here's a bare-bones example to create a centered button in a window:
# Import PySide libraries.
from PySide2 import QtCore
from PySide2 import QtWidgets
class MyWindow(QtWidgets.QWidget): # Create a class for our window, which inherits from `QWidget`
def __init__(self, parent=None): # The class's constructor.
super(MyWindow, self).__init__(parent) # Initialize its `QWidget` constructor method.
self.my_button = QtWidgets.QPushButton("My button!") # Create a button!
self.my_layout = QtWidgets.QVBoxLayout() # Create a vertical layout!
self.my_layout.setAlignment(QtCore.Qt.AlignCenter) # Center the horizontal alignment.
self.my_layout.addWidget(self.my_button) # Add the button to the layout.
self.setLayout(self.my_layout) # Make the window use this layout.
self.resize(300, 300) # Resize the window so it's not tiny.
my_window_instance = MyWindow() # Create an instance of our window class.
my_window_instance.show() # Show it!
Not too bad, right?

Python realtime mousedata with pyqtgraph

In my epic struggle to process raw mouse data on my ubuntu (14.04) OS with python, with alot help from here i am stuck again. It seems very hard for me to understand the "easyness" of pyqtgraph. all i want to do is to wrap the code i have now into a nice little gui with a start/pause/stop button, a list widget to show the numbers and a plot to let me see whats happening. I guess the main problem for me is, that i dont quite understand this whole event thing in pyqt.
anyway taking a "easy" example which has the widgets i want, i fail to implement my code(edited more minimalistic):
#!/usr/bin/python
import threading
import struct
import time
import numpy as np
from PyQt4 import QtGui # (the example applies equally well to PySide)
from PyQt4 import QtCore
import pyqtgraph as pg
##
data =[(0,0)]
sum_data = [(0,0)]
file = open("/dev/input/mouse2", "rb")
def getMouseEvent():
buf = file.read(3);
#python 2 & 3 compatibility
button = buf[0] if isinstance(buf[0], int) else ord(buf[0])
x,y = struct.unpack( "bb", buf[1:] );
print x,y
return x, y
def mouseCollect():
while True:
data.append(getMouseEvent())
sum_data.append(tuple(map(sum,zip(sum_data[-1],data[-1]))))
plot.plot(sum_data[0], clear=True)
pg.QtGui.QApplication.processEvents()
print sum_data[-1]
## Always start by initializing Qt (only once per application)
app = QtGui.QApplication([])
## Define a top-level widget to hold everything
w = QtGui.QWidget()
## Create some widgets to be placed inside
btn1 = QtGui.QPushButton('Start')
listw = QtGui.QListWidget()
plot = pg.PlotWidget()
def start_btn():
print 'test'
threading.Thread(target=mouseCollect).start()
btn1.clicked.connect(start_btn)
## Create a grid layout to manage the widgets size and position
layout = QtGui.QGridLayout()
w.setLayout(layout)
## Add widgets to the layout in their proper positions
layout.addWidget(btn1, 0, 0) # button goes in upper-left
layout.addWidget(plot, 0, 1, 4, 1)
## Display the widget as a new window
w.show()
## Start the Qt event loop
app.exec_()
##------------------------------------------------------------------
when i press the start button, the window just freezes and nothing happens. my thought was, if i press the button, it connects to the methot state there and it just doing its stuff. okay i have an infinite loop, but at least i thought i should see something. any help is apreciated, also any tips for a good reading about the matter is very welcome.
regards
Edit: inserted a thread as suggested by echocage
The best approach for repetitive updates is to use a QTimer. In this way you allow program control to go back to the Qt event loop after every update (no infinite loops allowed), and Qt periodically invokes your update function for you.
For example, see one of the many the updating plot examples included with pyqtgraph: https://github.com/pyqtgraph/pyqtgraph/blob/develop/examples/Plotting.py#L58
Threading is very difficult to do correctly and when it's done wrong you usually end up with crashing that is difficult to debug. I recommend to avoid threading until you are very confident with the event system and familiar with the pitfalls of threading.
I finally found it. I somehow never realized, that you HAVE to use numpy arrays. also i didnt use curve.setdata for plotting.
the more or less final code(not full code) now looks like this:
class mouseCollect(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run (self):
global e, curve, curve2, data1, data2
while e.wait():
tmp = getMouseEvent() #returns tuples in form of (x,y)
data1 = np.append(data1, tmp[0] )
data2 = np.append(data2, tmp[1] )
sum1= np.cumsum(data1)
sum2= np.cumsum(data2)
curve.setData(y=sum1)
curve2.setData(y=sum2)
guiPE # process event thingy
def stop(self):
e.clear()
well, it is a not really written efficiently, but it works :)

Error "QObject::startTimer: QTimer can only be used with threads started with QThread" many times when closing application

I know this has been asked many times before. I read all of those threads, and my case seems different. Everybody else who has this trouble has a few straightforward causes that I think I’ve ruled out, such as:
Starting a timer with no event loop running
Starting/stopping a timer from a thread other than the one that created the timer
Failing to set the parent property of a widget, leading to problems with the order of destruction
Below I have a minimal code sample that demonstrates the problem. Notice that I’ve started no threads or timers. I also have set the parent of every widget. If I remove the graph widgets, the problem goes away, so one is tempted to blame pyQtGraph, however, if I include the plot widgets but exclude all the blank tabs (i.e. every tab except tabCatchaTiger), the problem also goes away, and that seems to vindicate pyQtGraph.
Versions:
Windows 7
Python 2.7.8
Wing IDE 5.0.9-1
PyQt 4.11.1
PyQwt 5.2.1
PyQtGraph 0.9.8
Test case:
from PyQt4 import Qt, QtGui, QtCore
import PyQt4.Qwt5 as Qwt
import pyqtgraph as pg
pg.functions.USE_WEAVE = False # Lets pyqtgraph plot without gcc
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
# GUI for visualizing data from database
class crashyGUI(QtGui.QWidget) :
def __init__(self) :
# Make the window
QtGui.QWidget.__init__(self)
self.resize(700, QtGui.QDesktopWidget().screenGeometry(self).height()*.85)
self.setWindowTitle('Data Visualization')
# Create tab interface
tabWidget = QtGui.QTabWidget(self)
# define the tab objects
self.tabEeny = QtGui.QWidget(tabWidget)
self.tabMeeny = QtGui.QWidget(tabWidget)
self.tabMiney = QtGui.QWidget(tabWidget)
self.tabMoe = QtGui.QWidget(tabWidget)
self.tabCatchaTiger = QtGui.QWidget(tabWidget)
self.tabByThe = QtGui.QWidget(tabWidget)
self.tabToe = QtGui.QWidget(tabWidget)
# Initialize the tab objects
self.initTabCatchaTiger()
###########################################
############### Main Layout ###############
###########################################
tabWidget.addTab(self.tabEeny, 'Eeny')
tabWidget.addTab(self.tabMeeny, 'Meeny')
tabWidget.addTab(self.tabMiney, 'Miney')
tabWidget.addTab(self.tabMoe, 'Moe')
tabWidget.addTab(self.tabCatchaTiger, 'Catch a Tiger')
tabWidget.addTab(self.tabByThe, 'By The')
tabWidget.addTab(self.tabToe, 'Toe')
self.mainLayout = QtGui.QVBoxLayout(self)
self.mainLayout.addWidget(tabWidget)
self.setLayout(self.mainLayout)
def initTabCatchaTiger(self):
###########################################
############# ADC Capture Tab #############
###########################################
# define tab layout
grid = QtGui.QGridLayout(self.tabCatchaTiger)
# create copy of adc plot and add to row 3 of the grid
self.catchaTigerPlot1 = pg.PlotWidget(name = 'Catch a Tiger 1', parent = self.tabCatchaTiger)
self.catchaTigerPlot1.setTitle('Catch a Tiger 1')
grid.addWidget(self.catchaTigerPlot1, 2, 0, 1, 8)
self.catchaTigerPlot2 = pg.PlotWidget(name = 'Catch a Tiger 2', parent = self.tabCatchaTiger)
self.catchaTigerPlot2.setTitle('Catch a Tiger 2')
grid.addWidget(self.catchaTigerPlot2, 3, 0, 1, 8)
# set layout for tab
self.tabCatchaTiger.setLayout(grid)
def closeEvent(self, event) :
pass
def main() :
# open a QApplication and dialog() GUI
app = QtGui.QApplication([])
windowCrashy = crashyGUI()
windowCrashy.show()
app.exec_()
main()
There seem to be two closely-related issues in the example.
The first one causes Qt to print the QObject::startTimer: QTimer can only be used with threads started with QThread messages on exit.
The second one (which may not affect all users) causes Qt to print QPixmap: Must construct a QApplication before a QPaintDevice, and then dump core on exit.
Both of these issues are caused by python deleting objects in an unpredicable order when it exits.
In the example, the second issue can be fixed by adding the following line to the __init__ of the top-level window:
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Unless QApplication.setQuitOnLastWindowClosed has been changed to False, this will ensure that the application quits at the right time, and that Qt has a chance to automatically delete all the children of the top-level window before the python garbage-collector gets to work.
However, for this to be completely successful, all the relevant objects must be linked together in a parent-child hierarchy. The example code does this where it can, but there seem to be some critical places in the initialization of the PlotWidget class where it is not done.
In particular, there is nothing to ensure that the central item of the PlotWidget has a parent set when it is created. If the relevant part of the code is changed to this:
class PlotWidget(GraphicsView):
...
def __init__(self, parent=None, background='default', **kargs):
GraphicsView.__init__(self, parent, background=background)
...
self.plotItem = PlotItem(**kargs)
# make sure the item gets a parent
self.plotItem.setParent(self)
self.setCentralItem(self.plotItem)
then the first issue with the QTimer messages also goes away.
Here's a better answer:
You are allowing the QApplication to be collected before python exits. This causes two different issues:
The QTimer error messages are caused by pyqtgraph trying to track its ViewBoxes after the QApplication has been destroyed.
The crash appears to be intrinsic to Qt / PyQt. The following crashes in the same way:
from PyQt4 import Qt, QtGui, QtCore
def main() :
app = QtGui.QApplication([])
x = QtGui.QGraphicsView()
s = QtGui.QGraphicsScene()
x.setScene(s)
x.show()
app.exec_()
main()
You can fix it by adding global app to your main function, or by creating the QApplication at the module level.
Try to write this in block of __init__:
self.setAttribute(Qt.WA_DeleteOnClose)
Personally, I don't put any effort into chasing exit crashes anymore--just use pg.exit() and be done with it.
(but if you do happen to find a bug in pyqtgraph, don't hesitate to open an issue on github)
I had this happen as well and in my case it was caused by a call to deleteLater() on the aboutToQuit-Signal of the application, like so:
def closeEvent(self, *args, **kwargs):
self.deleteLater()
if __name__ == "__main__":
application = QtWidgets.QApplication(sys.argv)
window = testApplication()
# Handle application exit
application.aboutToQuit.connect(window.closeEvent)
# System exit
sys.exit(application.exec_())
Getting rid of the deleteLater on the whole window seemed to solve it.

QApplication' is not defined

I downloaded the script below from http://www.pythoncentral.io/pyside-pyqt-tutorial-interactive-widgets-and-layout-containers/
I get the following error message: NameError: name 'QApplication' is not defined
I added the first two lines of the script.
That did not help.
I thought maybe I must not have qt installed. But when I tried to run PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x32.exe, the program told me it was already installed.
Does anyone have any suggestions?
marc
# copied from http://www.pythoncentral.io/pyside-pyqt-tutorial-interactive-widgets-and-layout-containers/
# Every Qt application must have one and only one QApplication object;
# it receives the command line arguments passed to the script, as they
# can be used to customize the application's appearance and behavior
import sys
from PyQt4 import QtGui, QtCore
#import PyQt4.QtGui, PyQt4.QtCore
qt_app = QApplication(sys.argv)
class AbsolutePositioningExample(QWidget):
''' An example of PySide absolute positioning; the main window
inherits from QWidget, a convenient widget for an empty window. '''
def __init__(self):
# Initialize the object as a QWidget
QWidget.__init__(self)
# We have to set the size of the main window
# ourselves, since we control the entire layout
self.setMinimumSize(400, 185)
self.setWindowTitle('Dynamic Greeter')
# Create the controls with this object as their parent and set
# their position individually; each row is a label followed by
# another control
# Label for the salutation chooser
self.salutation_lbl = QLabel('Salutation:', self)
self.salutation_lbl.move(5, 5) # offset the first control 5px
# from top and left
self.salutations = ['Ahoy',
'Good day',
'Hello',
'Heyo',
'Hi',
'Salutations',
'Wassup',
'Yo']
# Create and fill the combo box to choose the salutation
self.salutation = QComboBox(self)
self.salutation.addItems(self.salutations)
# Allow 100px for the label and 5px each for borders at the
# far left, between the label and the combobox, and at the far
# right
self.salutation.setMinimumWidth(285)
# Place it five pixels to the right of the end of the label
self.salutation.move(110, 5)
# The label for the recipient control
self.recipient_lbl = QLabel('Recipient:', self)
# 5 pixel indent, 25 pixels lower than last pair of widgets
self.recipient_lbl.move(5, 30)
# The recipient control is an entry textbox
self.recipient = QLineEdit(self)
# Add some ghost text to indicate what sort of thing to enter
self.recipient.setPlaceholderText(""e.g. 'world' or 'Matey'"")
# Same width as the salutation
self.recipient.setMinimumWidth(285)
# Same indent as salutation but 25 pixels lower
self.recipient.move(110, 30)
# The label for the greeting widget
self.greeting_lbl = QLabel('Greeting:', self)
# Same indent as the others, but 45 pixels lower so it has
# physical separation, indicating difference of function
self.greeting_lbl.move(5, 75)
# The greeting widget is also a label
self.greeting = QLabel('', self)
# Same indent as the other controls
self.greeting.move(110, 75)
# The build button is a push button
self.build_button = QPushButton('&Build Greeting', self)
# Place it at the bottom right, narrower than
# the other interactive widgets
self.build_button.setMinimumWidth(145)
self.build_button.move(250, 150)
def run(self):
# Show the form
self.show()
# Run the Qt application
qt_app.exec_()
# Create an instance of the application window and run it
app = AbsolutePositioningExample()
app.run()
If you read through the tutorial in order, you'd see that the previous article in the series showed the stuff you need to part at the start of each fragment to make it a runnable program. The author apparently did this so that the same code could be used with both PyQt and PySide.
So, if you're using PyQt4, you'll need to add this:
# Allow access to command-line arguments
import sys
# SIP allows us to select the API we wish to use
import sip
# use the more modern PyQt API (not enabled by default in Python 2.x);
# must precede importing any module that provides the API specified
sip.setapi('QDate', 2)
sip.setapi('QDateTime', 2)
sip.setapi('QString', 2)
sip.setapi('QTextStream', 2)
sip.setapi('QTime', 2)
sip.setapi('QUrl', 2)
sip.setapi('QVariant', 2)
# Import all of Qt
from PyQt4.Qt import *
If PySide:
# Allow access to command-line arguments
import sys
# Import the core and GUI elements of Qt
from PySide.QtCore import *
from PySide.QtGui import *
Below the box showing your this boilerplate, there's a nice, readable explanation of what it all means and why you need to do it.
However, I'd suggest that if you're trying to learn from a tutorial, you start at the start and work forward, instead of starting in the middle and trying to figure out what you missed along the way.
If you just do from PyQt4 import QtGui, QtCore instead of importing * from them, the names in those modules are available, but only as qualified names. That is, instead of QApplication, you have to write QtCore.QApplication.
If you don't understand the difference, read Modules in the official Python tutorial, or something equivalent, to learn how imports work.
I guess that import is wrong, it should be:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
For anyone experiencing this issue in PyQt5, try using QCoreApplication instead and it should work.

Carcass of QProgressDialog lingers - sometimes

progress = QtGui.QProgressDialog("Parsing Log", "Stop", 0,numberOfLinesInFile , self)
progress.setWindowModality(QtCore.Qt.WindowModal)
for lineNumber, line in enumerate(file):
# yield a bit to the Qt UI handler
QtGui.QApplication.processEvents()
progress.setValue(lineNumber + 1) # lineNumber is zero-based so need the plus one to match the more literal numberOfLinesInFile
if progress.wasCanceled():
progressWasCancelled = True
break
# ...read and parse lines from file (20mb takes ~10 seconds)
# crank the progress bar through to completion to get rid of it
# this seems to forgo the opportunity to use progress.wasCanceled() subsequently?
progress.setValue(numberOfLinesInFile)
if not progressWasCancelled:
self.updateTable(self.requestRoster)
After this, and regardless of the progress dialogue being cancelled or not, the progress dialogue is hidden (it slides back up into the toolbar). But if I switch application ('command tab' on the Mac) then switch back to my application, a ghost of the QProgressDialog is in front of the main application window! Its progress bar is at 100% and the stop button is blue but not pulsing. It is unresponsive. If I move the application window it disappears.
If I call progress.destroy() after progress.setValue(numberOfLinesInFile) that seems to help. But it seems worrying to copy the example from the docs and get bitten, and I don't know the ramifications of destroy().
I was using PySide, I switched to PyQt and same thing.
Also, sometimes progress.setValue(numberOfLinesInFile) causes subsequent reads of progress.wasCancelled() to return false (but sometimes it returns true!) which is why I set my own progressWasCancelled. Its randomness is disturbing.
I'm on Mac 10.6.8, Qt 4.8.2, Python 2.7. Tried with PySide 1.1.0 and PyQt 4.9.4.
Am I doing this all wrong?
I can't test on a Mac, but I'll try to make a few suggestions which could help solve your issues.
Firstly, if you use a modal progress dialog, there's no need to call processEvents(), as the dialog will handle this itself.
Secondly, this line in your code:
progress.setValue(lineNumber + 1)
is problematic, because to quote the Qt docs:
For the progress dialog to work as expected, you should initially set this property to 0 and finally set it to QProgressDialog::maximum(); you can call setValue() any number of times in-between.
so you should either call progress.setValue(0) before the loop, or just avoid adding the offset altogether. Also, on the final iteration, lineNumber + 1 will equal the maximum, which will reset the dialog at that point (unless autoReset has been set to False). It is for this reason that the Qt example calls setValue(maximum) after the loop has completed.
Finally, there is no problem with calling destroy() or deleteLater() after you've finished with the progress dialog - in fact, it's a good idea. When you pass self to the QProgressDialog constructor, it will become the parent of the dialog and keep a reference to it. So, unless you explicitly delete it, a new child dialog (plus all it's child objects) will be added every time you call the function that uses it (which could potentially waste a lot of memory).
Here's a demo script that may be improvement:
import sys, time
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
def handleButton(self):
file = range(30)
numberOfLinesInFile = len(file)
progressWasCancelled = False
progress = QtGui.QProgressDialog(
"Parsing Log", "Stop", 0, numberOfLinesInFile, self)
progress.setWindowModality(QtCore.Qt.WindowModal)
progress.setMinimumDuration(0)
for lineNumber, line in enumerate(file):
progress.setValue(lineNumber)
if progress.wasCanceled():
progressWasCancelled = True
break
time.sleep(0.05)
progress.setValue(numberOfLinesInFile)
print 'cancelled', progress.wasCanceled(), progressWasCancelled
progress.deleteLater()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Categories