I am making a wxPython application that needs to work in full screen. I want to use the new full screen mode that came in OS X Lion. How can I make the full screen icon appear on the top right corner?
Until bug #14357 is fixed, there's no direct way to do this using only wxPython functions that I know of.
However, you can bypass wxWidgets and access the Cocoa APIs directly to do what you need. Note that you must be using the wxMac/Cocoa bindings (wxPython 2.9 or above).
This is the code necessary to make a frame full-screen capable:
# from http://stackoverflow.com/questions/12328143/getting-pyobjc-object-from-integer-id
import ctypes, objc
_objc = ctypes.PyDLL(objc._objc.__file__)
# PyObject *PyObjCObject_New(id objc_object, int flags, int retain)
_objc.PyObjCObject_New.restype = ctypes.py_object
_objc.PyObjCObject_New.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
def objc_object(id):
return _objc.PyObjCObject_New(id, 0, 1)
def SetFullScreenCapable(frame):
frameobj = objc_object(frame.GetHandle())
NSWindowCollectionBehaviorFullScreenPrimary = 1<<7
window = frameobj.window()
newBehavior = window.collectionBehavior() | NSWindowCollectionBehaviorFullScreenPrimary
window.setCollectionBehavior_(newBehavior)
And here's a short test app that demonstrates it:
import wxversion
wxversion.select('2-osx_cocoa') # require Cocoa version of wxWidgets
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.Bind(wx.EVT_CLOSE, self.OnClose)
wx.Button(self, label="Hello!") # test button to demonstrate full-screen resizing
SetFullScreenCapable(self)
def OnClose(self, event):
exit()
app = wx.App()
frame = Frame()
frame.Show()
app.MainLoop()
Related
I'm currently developing a PyGObject app and I'm having issues selecting specific children in a Gtk+ FlowBox. Even after selecting the FlowBox selection mode (SINGLE) populating the FlowBox and writing code to select a specific child, the first child is always selected.
#!/usr/bin/python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
class App(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="App")
flowbox = Gtk.FlowBox()
flowbox.set_valign(Gtk.Align.START)
flowbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
# Drawing 3 squares
flowbox.add(self.drawing_area())
flowbox.add(self.drawing_area())
flowbox.add(self.drawing_area())
child = flowbox.get_child_at_index(2)
flowbox.select_child(child)
flowbox.queue_draw()
self.add(flowbox)
def drawing_area(self):
preview = Gtk.DrawingArea()
preview.connect("draw", self.draw_square)
preview.set_size_request(150, 150)
return preview
def draw_square(self, widget, cr):
cr.scale(150, 150)
style_context = widget.get_style_context()
color = style_context.get_color(Gtk.StateFlags.NORMAL)
cr.set_source_rgba(*color)
cr.rectangle(0, 0, 1, 1)
cr.fill()
window = App()
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()
Even though I choose to select the child at index 2, the app only ever shows the first child being selected:
Screenshot of above code running
The strange part is that when I check to see which child is selected using the following code (placed before the "self.add(flowbox)" line), the Terminal displays that the child I specified to be selected (at index 2) is the only selected child, even though the window only shows the first child being selected:
for child in flowbox.get_selected_children():
print child.get_index()
I think you have located a bug in GTK, it seems that something in show_all is messing up. My first guess was that it was caused by the fact that the FlowBox wasn't realized so I changed your code to use the show signal (realize but show is emitted later) and checked whether it still happend. Sadly it was..
So I got the feeling that something else was off so just a quick test added self.show() right after Gtk.Window.__init__ this made the selection work but made the Flowbox wider than needed (probably because of the default width of a empty window). So I added the self.show() in the listener and this actually solved the issue.
The complete code is as follows, but as it is a dirty workaround you should still report this bug.
#!/usr/bin/python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
class App(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="App")
self.flowbox = Gtk.FlowBox()
self.flowbox.set_valign(Gtk.Align.START)
self.flowbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
# Drawing 3 squares
self.flowbox.add(self.drawing_area())
self.flowbox.add(self.drawing_area())
self.flowbox.add(self.drawing_area())
self.flowbox.connect("show", self.on_realize)
self.add(self.flowbox)
def on_realize(self, flowbox):
# The creative workaround/hack
self.show()
child = self.flowbox.get_child_at_index(2)
self.flowbox.select_child(child)
def drawing_area(self):
preview = Gtk.DrawingArea()
preview.connect("draw", self.draw_square)
preview.set_size_request(150, 150)
return preview
def draw_square(self, widget, cr):
cr.scale(150, 150)
style_context = widget.get_style_context()
color = style_context.get_color(Gtk.StateFlags.NORMAL)
cr.set_source_rgba(*color)
cr.rectangle(0, 0, 1, 1)
cr.fill()
window = App()
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()
This question is maybe related to another SO question.
I'm running MacOS 10.11.2 El Capitan. Wishing rich GUI features
around my OpenGL applications I decided to give PyQT5 a shot to
create the OpenGL context so I can integrate my OpenGL as a QtWidget int a GUI application.
QT5 provides several API methods for QGlWidget which I summarize shortly here:
initializeGL: gets invoked once before paintGL
paintGL: place to draw stuff to active frame buffer
I'm able to create the widget and initialize shaders etc. But when it comes to framebuffer related operations like glClear an error appears:
File "errorchecker.pyx", line 53, in OpenGL_accelerate.errorchecker._ErrorChecker.glCheckError (src/errorchecker.c:1218)
OpenGL.error.GLError: GLError(
err = 1286,
description = 'invalid framebuffer operation',
baseOperation = glClear,
cArguments = (GL_COLOR_BUFFER_BIT,)
)
I found a website reporting about related issue. It seems that there is no framebuffer configured when the API methods get invoked. As I feel it should be the task of QT I did not try to configure window framebuffer myself. But I found that after some calls of the API methods the framebuffer was created magically. Thus I build a little hack which will wait until paintGL was invoked NSKIP_PAINTGL=3 times. Then I configure my object so the normal paintGL process starts to work. This seems to work. But sometimes it needs more than NSKIP_PAINTGL times, so I included a little sleep within the workaround. QT seems to create the frame buffer a little after it should. Maybe QT does it in a separate thread? The QOpenGLWidget confirm that the framebuffer may not be created at some time:
Returns The frame buffer object handle or 0 if not yet initialized.
I don't like work arounds like this, since I am afraid of raised conditions here. Also I do not have a lot of control here (I need to rely on the fact that QT invokes paintGL often enough in first place so the hack can work). I'm not familiar with QT framework at the moment so here is my question:
How can I create some kinda loop which, when QGLControllerWidget was created, runs updateGL methods covered by a try/catch and retries as long as the GlError appears? Alternatively the loop may listen to QOpenGLWidget::defaultFramebufferObject() and waits for an object handle.
Of course I want to integrate this hack as elegant as possible into QT application flow - doing it the cute way.
Or did I miss something here? Is it possible to setup PyQT in some way so it won't invoke the OpenGL API methods before a valid framebuffer exists?
Here is an isolated code with the hack which runs on my Mac:
from PyQt5 import QtGui, QtCore, QtOpenGL, QtWidgets
from PyQt5.QtOpenGL import QGLWidget
from OpenGL.GL import *
from time import sleep
NSKIP_PAINTGL = 3
class QGLControllerWidget(QGLWidget):
"""
basic test widget: a black screen which clears
framebuffer on paintGL event so it should stay
black on resize and so on.
"""
def __init__(self, format = None):
super(QGLControllerWidget, self).__init__(format, None)
self._weird_pyqt5_framebuffer_hack = 0
# replace paintGL by workaround
self._weird_pyqt5_framebuffer_hack_original_paintGL = self.paintGL
self.paintGL = self._weird_pyqt5_framebuffer_hack_paintGL
def initializeGL(self):
pass
def _weird_pyqt5_framebuffer_hack_paintGL(self):
self._weird_pyqt5_framebuffer_hack += 1
if self._weird_pyqt5_framebuffer_hack < NSKIP_PAINTGL:
return
sleep(0.1)
# restore original paintGL
self.paintGL = self._weird_pyqt5_framebuffer_hack_original_paintGL
self.updateGL()
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT)
if __name__ == '__main__':
import sys
class QTWithGLTest(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(QTWithGLTest, self).__init__(parent)
# MacOS core profile 4.1
qgl_format = QtOpenGL.QGLFormat()
qgl_format.setVersion(4, 1)
qgl_format.setProfile(QtOpenGL.QGLFormat.CoreProfile)
qgl_format.setSampleBuffers(True)
self.widget = QGLControllerWidget(qgl_format)
self.setCentralWidget(self.widget)
self.show()
app = QtWidgets.QApplication(sys.argv)
window = QTWithGLTest()
window.show()
app.exec_()
Note that also C++ snippets are welcome as I will try convert then into python.
The QGL* stuff is deprecated in Qt5. That might be the reason why it's calling paintGL() ahead of time. You should try to use QOpenGLWidget or QOpenGLWindow instead.
Alternatively, you could try to use the isValid() method inside paintGL() and bail out early if it returns a falsy value:
def paintGL(self):
if not self.isValid():
return
If you want to try QOpenGLWidget, you can use this as starting point:
#!/usr/bin/env python
from PyQt5.QtGui import (
QOpenGLBuffer,
QOpenGLShader,
QOpenGLShaderProgram,
QOpenGLVersionProfile,
QOpenGLVertexArrayObject,
QSurfaceFormat,
)
from PyQt5.QtWidgets import QApplication, QMainWindow, QOpenGLWidget
class QTWithGLTest(QMainWindow):
"""Main window."""
def __init__(self, versionprofile=None, *args, **kwargs):
"""Initialize with an OpenGL Widget."""
super(QTWithGLTest, self).__init__(*args, **kwargs)
self.widget = QOpenGLControllerWidget(versionprofile=versionprofile)
self.setCentralWidget(self.widget)
self.show()
class QOpenGLControllerWidget(QOpenGLWidget):
"""Widget that sets up specific OpenGL version profile."""
def __init__(self, versionprofile=None, *args, **kwargs):
"""Initialize OpenGL version profile."""
super(QOpenGLControllerWidget, self).__init__(*args, **kwargs)
self.versionprofile = versionprofile
def initializeGL(self):
"""Apply OpenGL version profile and initialize OpenGL functions."""
self.gl = self.context().versionFunctions(self.versionprofile)
if not self.gl:
raise RuntimeError("unable to apply OpenGL version profile")
self.gl.initializeOpenGLFunctions()
self.createShaders()
self.createVBO()
self.gl.glClearColor(0.0, 0.0, 0.0, 0.0)
def paintGL(self):
"""Painting callback that uses the initialized OpenGL functions."""
if not self.gl:
return
self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT)
self.gl.glDrawArrays(self.gl.GL_TRIANGLES, 0, 3)
def resizeGL(self, w, h):
"""Resize viewport to match widget dimensions."""
self.gl.glViewport(0, 0, w, h)
def createShaders(self):
...
def createVBO(self):
...
if __name__ == '__main__':
import sys
fmt = QSurfaceFormat()
fmt.setVersion(4, 1)
fmt.setProfile(QSurfaceFormat.CoreProfile)
fmt.setSamples(4)
QSurfaceFormat.setDefaultFormat(fmt)
vp = QOpenGLVersionProfile()
vp.setVersion(4, 1)
vp.setProfile(QSurfaceFormat.CoreProfile)
app = QApplication(sys.argv)
window = QTWithGLTest(versionprofile=vp)
window.show()
sys.exit(app.exec_())
Also, you don't need to use the OpenGL functions provided by Qt like I did. You can use the plain functions from OpenGL.GL as well or you can even mix and match (Qt provides some nice classes that make working with GL easier). If you just want to use OpenGL.GL, you can remove everything referring to QOpenGLVersionProfile, versionprofile, versionFunctions, initializeOpenGLFunctions and self.gl.
On Windows 7 64-bit, using Python 2.7.3 and wx version 2.8.12.1.
I'm trying to get a wx.Frame styled as wx.SIMPLE_BORDER or wx.NO_BORDER to have a shadow like other windows, like this:
Drop Shadow
Unfortunately, this seems to be a part of the wx.RESIZE_BORDER style. Here is what happens without that:
No Shadow
So, I went to Googling and found this ArtManager class in the wx.lib.agw.artmanager module, which had a DropShadow function, claiming that it "Adds a shadow under the window (Windows Only)."
But, it didn't seem to do anything. No errors, but no results. Perhaps it doesn't work on Windows 7, but I'm hoping you have a better answer. Of course, I'd be open to other methods of creating a shadow, but this is the one I had found.
Here's my code:
import wx
import wx.lib.agw.artmanager
class main(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, style=wx.SIMPLE_BORDER, size=(469, 400))
am = wx.lib.agw.artmanager.ArtManager()
self.Center()
self.Show()
am.DropShadow(self, True)
class app(wx.App):
def OnInit(self):
frame = main(None, -1)
frame.Show(True)
return True
app = app()
app.MainLoop()
Background
I am about to resolve another issue, which consists in reserving screen-space for a Qt Window on X11. For this i use PyQt4 and Python-Xlib.
Situation
The application i want to save screen space for is a frameless Qt Window, 25px high and screen-wide, it's a panel in fact. The simplified code looks like this:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
# this module i made myself (see below):
import myXwindow
class QtPanel(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# here i explicitely name the window:
self.setWindowTitle('QtPanel')
self.resize(QtGui.QDesktopWidget().screenGeometry().width(), 25)
self.move(0,0)
self.setWindowFlags(QtCore.Qt.Widget |
QtCore.Qt.FramelessWindowHint |
QtCore.Qt.WindowStaysOnTopHint)
self.setAttribute(QtCore.Qt.WA_X11NetWmWindowTypeDock)
def main():
app = QtGui.QApplication(sys.argv)
panel = QtPanel()
panel.show()
xwindow = myXwindow.Window(str(panel.windowTitle()))
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Now to reserve space for this QtPanel application, i need to call on xlib. Here below i will just show how i intend to 'grab' my window from X. The following module is imported in the code here above to get the Xwindow of my panel:
from Xlib.display import Display
from Xlib import X
class Window(object):
def __init__(self, title):
self._title = title
self._root = Display().screen().root
self._window = self.find_window()
def find_window(self):
my_window = None
display = Display()
windowIDs = self._root.get_full_property(
display.intern_atom('_NET_CLIENT_LIST'),
X.AnyPropertyType).value
print 'looking for windows:'
count = 0
for windowID in windowIDs:
count += 1
window = display.create_resource_object('window', windowID)
title = window.get_wm_name()
print 'window', count, ':', title
if self._title in title:
my_window = window
return my_window
Problem
Now when i run the QtPanel application, it should return a list of names of all the windows currently displayed by X. Despite having been named explicitly in the code (see here above) the QtPanel application either has no name (i.e. None or '') or is named 'x-nautilus-desktop', here is a sample of what was returned:
$ python ./Qtpanel.py
looking for windows:
window 1 : Bottom Expanded Edge Panel
window 2 : cairo-dock
window 3 : x-nautilus-desktop
window 4 : ReserveSpace - NetBeans IDE 6.9
window 5 : How to provide X11 with a wm_name for a Qt4 window? - Stack Overflow - Mozilla Firefox
Question
How can i 'name' my Qt Application or Qt toplevel window so it shows properly on X?
And/or: how can i identify my application in X other than with the application's name?
Although i don't quite know what the unnamed window is (and it seems related to launching the QtPanel application, the Xlib script is not able to find the window on display because it is queried before the event loop QApplication._exec(). A thread may be required to do that outside of the main loop.
I am currently using wx.CustomTree, to use to display a series of configuration settings. I generally fill them with wx.TextCtrl / wx.Combobox, to allow the user to edit / enter stuff. Here is my code:
class ConfigTree(CT.CustomTreeCtrl):
"""
Holds all non gui drawing panel stuff
"""
def __init__(self, parent):
CT.CustomTreeCtrl.__init__(self, parent,
id = common.ID_CONTROL_SETTINGS,
style = wx.TR_DEFAULT_STYLE | wx.TR_HAS_BUTTONS
| wx.TR_HAS_VARIABLE_ROW_HEIGHT | wx.TR_SINGLE)
#self.HideWindows()
#self.RefreshSubtree(self.root)
self.population_size_ctrl = None
self.SetSizeHints(350, common.FRAME_SIZE[1])
self.root = self.AddRoot("Configuration Settings")
child = self.AppendItem(self.root, "Foo", wnd=wx.TextCtrl(self, wx.ID_ANY, "Lots Of Muffins"))
The problem is, any children nodes, the data for these nodes is not filled in. When i basically expand the configuration settings tree node. I see the "Foo" node, however the textbox is empty. This is the same for both text node, Until i actually click on the child node. I've looked tried every form of update / etc. Does anyone have any ideas?
To: Anurag Uniyal
Firstly sorry for not giving the rest of the code. I've gotten around this problem by simply resizing the window everytime i demo the application.
So i tried the code on my Macbook Pro running Mac OS X, with newest wx and python 2.6. I still have the same problem, however i noticed resizing the window, or even touching the scrollbar fixes the issue.
I also noticed however, there is absolutely NO problems running on Windows Vista / Windows 7.
So trying this on another macbook running an older version of wx + python. Results in the same problem :(
Is there anyway to force the panel to redraw it self? Which i am pretty sure happens when i resize the window.
If you don't have any ideas then i'll strip it down and make a demo example, im home and won't be at work until later tommorow.
You can use RefreshItems if you are using virtual controls, or you could refresh the panel, which would update the contents of all the children windows (widgets).
I tested it on window with wx version 2.8.10.1 and it works, which OS and wx version you are using?
here is self contained code, which can be copy-pasted and run
import wx
import wx.lib.customtreectrl as CT
class ConfigTree(CT.CustomTreeCtrl):
"""
Holds all non gui drawing panel stuff
"""
def __init__(self, parent):
CT.CustomTreeCtrl.__init__(self, parent,
id = -1,
style = wx.TR_DEFAULT_STYLE | wx.TR_HAS_BUTTONS
| wx.TR_HAS_VARIABLE_ROW_HEIGHT | wx.TR_SINGLE)
self.population_size_ctrl = None
self.SetSizeHints(350, 350)
self.root = self.AddRoot("Configuration Settings")
child = self.AppendItem(self.root, "Foo", wnd=wx.TextCtrl(self, wx.ID_ANY, "Lots Of Muffins"))
def main():
app = wx.App()
frame = wx.Frame(None, title="Test tree", size=(500,500))
p = wx.Panel(frame, size=(500,500))
tree = ConfigTree(p)
tree.SetSize((500,500))
frame.Show()
app.MainLoop()
if __name__ == '__main__':
main()