Binding keyboard shortcuts to GLCanvas in wxPython - python-2.7

Is it possible to bind keyboard shortcuts (like Ctrl+z to Undo, Ctrl+Shift+z to Redo, Ctrl+c, Ctrl+v, etc.) to GLCanvas in wxpython? If so, can someone provide a minimal example, maybe something like drawing a sphere on single-click and then Undo and Redo with a shortcut?

import wx
from wx import glcanvas
class myFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='test')
self.gl = MyCanvasBase(self)
self.Show()
class MyCanvasBase(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, style=wx.WANTS_CHARS)
self.Bind(wx.EVT_CHAR, self.OnKeyDown) # for wx.WANTS_CHARS
# self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
def OnKeyDown(self, evt):
keycode = evt.GetKeyCode()
print keycode
if __name__ == '__main__':
app = wx.App(0)
Frame = myFrame()
app.MainLoop()
app.Destroy()
(edit) control+c etc can be captured by setting window style to wx.WANTS_CHARS. I made changes to use that style.
This is my attempt to make a minimum example of capturing key down events but without cube.
Have you looked at wxphython demo? That is one of the best learning sources of wxpython. If you install wxPython2.8-win32-docs-demos-2.8.12.1.exe, you will find an OpenGL sample code with a cube that you can rotate with mouse.

Related

Animated gif with wxpython phoenix

I was trying to put an animated gif in a wxpython panel but apparently there is no animarion nor adv package in my wxpython version:
In [1]: import wx
In [2]: wx.version()
Out[2]: '4.0.1 gtk3 (phoenix)'
Then i tried to use the gif as a wx.Bitmap but of course it would not play. I know that according to phoenix documention:
https://wxpython.org/Phoenix/docs/html/classic_vs_phoenix.html
the gif handler class is MISSING, but i was wondering if there is any way to use a gif (threding maybe?) in phoenix.
wx.adv contains Animation and AnimationCtrl
Ripped out of the demo's
import wx
from wx.adv import Animation, AnimationCtrl
class TestPanel(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
anim = Animation('/home/rolf/loveyourjob5qj.gif')
ctrl = AnimationCtrl(self, -1, anim)
ctrl.Play()
sizer.Add(ctrl)
self.SetSizerAndFit(sizer)
self.Show()
if __name__ == '__main__':
test=wx.App()
TestPanel(None)
test.MainLoop()

PyQt 4: Get Position of Toolbar

Hy guys,
in my executable program there is a toolbar. Well, the user decides to move the toolbar. Now the toolbar is floating. I know I have to conntect the floating-signals that is emittted when the toolbar ist arranged by the user. How can I save the new position of the toolbar? I know the method of adding the toolbar to the main window with a position:self.addToolBar( Qt.LeftToolBarArea , toolbar_name). In the handle_floating()-method you see what I want: There I want to get the position currently, but how? You also see I have just added one member variable, named self.toolbar_pos, to hold the position of the toolbar. My idea is, when application is terminated I want to serialize this value to a file, and later, when application is ran again its will read that file and set the toolbar accordingly. But this is no problem. Currently I don't have no idea to get the position of the toolbar.
I need your help :)
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.toolbar_pos = None
self.initUI()
def initUI(self):
exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(QtGui.qApp.quit)
self.toolbar = QtGui.QToolBar(self)
self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.addToolBar(self.toolbar )
self.toolbar.addAction(exitAction)
self.toolbar.setAllowedAreas(QtCore.Qt.TopToolBarArea
| QtCore.Qt.BottomToolBarArea
| QtCore.Qt.LeftToolBarArea
| QtCore.Qt.RightToolBarArea)
self.addToolBar( QtCore.Qt.LeftToolBarArea , self.toolbar )
self.toolbar.topLevelChanged.connect(self.handle_floating)
def handle_floating(self, event):
# The topLevel parameter is true
# if the toolbar is now floating
if not event:
# If the toolbar no longer floats,
# then calculate the position where the
# toolbar is located currently.
self.toolbar_pos = None
print "Get position: ?"
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.setGeometry(300, 300, 300, 200)
ex.setWindowTitle('Toolbar example')
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The QMainWindow class already has APIs for this: i.e. saveState and restoreState. These can be used to save and restore the state of all the toolbars and dock-widgets in your application.
To use them, you first need to make sure that all your toolbars and dock-widgets are given a unique object-name when they are created:
class Example(QtGui.QMainWindow):
...
def initUI(self):
...
self.toolbar = QtGui.QToolBar(self)
self.toolbar.setObjectName('foobar')
Then you can override closeEvent to save the state:
class Example(QtGui.QMainWindow):
...
def closeEvent(self, event):
with open('/tmp/test.conf', 'wb') as stream:
stream.write(self.saveState().data())
(NB: I've just used a temporary file here for testing, but it would obviously be much better to use something like QSettings in your real application).
Finally, you can restore the state that was saved previously:
class Example(QtGui.QMainWindow):
def __init__(self, parent=None):
...
self.initUI()
try:
with open('/tmp/test.conf', 'rb') as stream:
self.restoreState(QtCore.QByteArray(stream.read()))
except IOError:
pass

Exit button doesn´t work - wxpython

I developed a GUI with wxGlade and its still working. But to start the GUI - I coded a script with some choices.
So everything works but when I push the red button with the "x" to close the window - the application doesn´t stop.
I made a method, called by a separate exit button, which calls an exit-function in my script. But normally the users are using the close button (red button with X) so my method is not being used to close the window and the window doesn't get closed ultimately.
This is the exit-function.
def stopExport(self, event): # wxGlade: MyFrame.<event_handler>
self.Close() # close the Frame
from ExportManager import Exportmanager # import the exit function
Exportmanager().exit() # call it
How can I use this function with the red button with the "x"?
As per my understanding of your question, your application is not closing when you click on the close button (The red button with X on the right top corner.)
By default when you click the close button your application should close. In your case it seems to me that you have bind the EVT_CLOSE to some method, which has no code in it to close the app window.
For eg. consider the code snippet below, I have intentionally bind the EVT_CLOSE event to a method named as closeWindow(). This method does nothing that is why I have the pass keyword there. Now if you execute the code snippet below you can see that the app window won't close.
Code:
import wx
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 500
screenHeight = 400
screenSize = (screenWidth,screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.Bind(wx.EVT_CLOSE, self.closeWindow) #Bind the EVT_CLOSE event to closeWindow()
def closeWindow(self, event):
pass #This won't let the app to close
if __name__=='__main__':
app = wx.App(False)
frame = GUI(parent=None, id=-1, title="Problem Demo-PSS")
frame.Show()
app.MainLoop()
So, in order to close the app window you need to change the closeWindow(). For eg: Following code snippet will use the Destroy() close the app window when you click on the close button.
import wx
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 500
screenHeight = 400
screenSize = (screenWidth,screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.Bind(wx.EVT_CLOSE, self.closeWindow) #Bind the EVT_CLOSE event to closeWindow()
def closeWindow(self, event):
self.Destroy() #This will close the app window.
if __name__=='__main__':
app = wx.App(False)
frame = GUI(parent=None, id=-1, title="Problem Demo-PSS")
frame.Show()
app.MainLoop()
I hope it was useful.

How to handle several keys pressed at the same time in Kivy?

I was planning on doing a little crossplatform game with kivy and, when I was testing the way to get input from the keyboard on the pc, I had a little surprise.
Kivy doesn't seem to handle several keys pressed at same with it's on_keyboard_down events, when you press more than one key at same in kivy, the keyboard class used in the official documentation passes the last pressed key in change of all the keys being pressed at the moment.
It looks like the keyboard class is designed to let the user type in the app because when you press a key for seconds, there's a little delay between the first key event and the rest of them (which are finally one per step), just like the one you can notice when typing.
This is the code I wrote for a keyboard-interactive hello world.
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
from kivy.core.window import Window
class MyApp(App):
def __init__(self, **kwargs):
super(MyApp, self).__init__(**kwargs)
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down = self._on_keyboard_down)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down = self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
print str(keyboard)+' '+str(keycode[1])+' '+str(text)+' '+str(modifiers)
if keycode[1] == 'w':
self.moveable.y += 1
if keycode[1] == 's':
self.moveable.y -= 1
if keycode[1] == 'd':
self.moveable.x += 1
if keycode[1] == 'a':
self.moveable.x -= 1
def build(self):
self.moveable = Scatter()
self.moveable.add_widget( Label(text = 'Hello moving world!') )
return self.moveable
if __name__ == '__main__':
MyApp().run()
Because of what I wrote before, this doesn't seem like the kind of keyboard input I'd use for a video game, and I haven't found anywhere how to get it better for that purpose.
Sorry if the question is inappropiate for some reason, I tried to find the answer to this, but I couldn't, so here it is.
Thanks you a lot in advance if you can help with it.
It looks like the keyboard class is designed to let the user type in the app because when you press a key for seconds, there's a little delay between the first key event and the rest of them
(Edit: Having reread your post, I think you don't want to change this at all and have misunderstood how to work with keyboard input, see the second part of this post)
I'm pretty sure this is down to operating system settings, not to do with Kivy. There's plenty of discussion about changing this value online if you want to, for instance on linux you can probably do xset r rate [delay] [rate], e.g. xset r rate 200 25, or in various desktop environments there's a gui setting for it. Similar things are true on windows. The keyword seems to be 'repeat delay'.
That said, I'm not sure why this actually matters. Kivy tells you when the key is pressed and when it is released, and you can perform an event however often you like during that time if you want to. Why does it matter whether the os keeps sending you extra keypresses?
Kivy doesn't seem to handle several keys pressed at same with it's on_keyboard_down events, when you press more than one key at same in kivy, the keyboard class used in the official documentation passes the last pressed key in change of all the keys being pressed at the moment.
I think you might be misunderstanding how Kivy is presenting you this information. When a key is pressed, kivy passes you an on_key_down event for that key. When it is released, kivy passes an on_key_up event for that key (your program doesn't do anything with this). In between those events you know the key is still pressed, it doesn't matter whether the system keeps feeding you fake keypresses.
Edit: Here is a modified version of your program that prints on_key_up event information as well:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
from kivy.core.window import Window
class MyApp(App):
def __init__(self, **kwargs):
super(MyApp, self).__init__(**kwargs)
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down = self._on_keyboard_down)
self._keyboard.bind(on_key_up = self._on_keyboard_up)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down = self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, *args):
print 'down', args
def _on_keyboard_up(self, *args):
print 'up', args
def build(self):
self.moveable = Scatter()
self.moveable.add_widget( Label(text = 'Hello moving world!') )
return self.moveable
if __name__ == '__main__':
MyApp().run()

Events with QGraphicsItemGroup

In my application I want to use QGraphicsItemGroup for grouping items into one item.
I played with it a little and not sure using it because when I want to catch events, events are merged together but I want to handle specific event with specific child.
How can I achieve this?
You need to call QGraphicsItemGroup::setHandlesChildEvents(false). This stops the QGraphicsItemGroup trying to handle the event, and lets the child QGraphicsItems handle them instead.
I think that's the point of the QGraphicsItemGroup. Judging from the documentation, this is meant to simplify moving and transforming multiple items at once e.g. imagine the following case: a user draws a selection rectangle around several items in an application because he wants to move all of them. Perhaps what you want more is to create a hierarchy of items, e.g. have one parent item with several child items. This way you'll get the individual events for each item. This can be accomplished by calling QGraphicsItem::setParentItem();
The question doesn't specify which version of Qt is concerned and there is a correct answer for Qt4. Here is an answer for Qt5 (it works for PyQt5 and I guess it'll work also in C++). The solution was to reimplement sceneEvent, reimplementing a specialized event-catcher such as contextMenuEvent was not sufficient.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
class GraphicsItem(QtWidgets.QGraphicsItem):
def __init__(self,
rect: QtCore.QRectF,
name: str,
parent: QtWidgets.QGraphicsItem = None):
super().__init__(parent)
self._name = name
self._rect = rect
def boundingRect(self):
return self._rect
def paint(self,
painter: QtGui.QPainter,
option: QtWidgets.QStyleOptionGraphicsItem,
widget: QtWidgets.QWidget = None):
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.red))
painter.drawRect(self._rect)
def sceneEvent(self, event: QtCore.QEvent):
if (event.type() == QtCore.QEvent.GraphicsSceneContextMenu):
self.contextMenuEvent(event)
event.accept()
return True
def contextMenuEvent(self, event: QtWidgets.QGraphicsSceneContextMenuEvent):
print(f'contextMenuEvent in "{self._name}"')
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._scene = QtWidgets.QGraphicsScene()
layout = QtWidgets.QHBoxLayout()
self._view = QtWidgets.QGraphicsView(self._scene)
layout.addWidget(self._view)
self._widget = QtWidgets.QWidget()
self._widget.setLayout(layout)
group = QtWidgets.QGraphicsItemGroup()
self._scene.addItem(group)
scene_item = GraphicsItem(QtCore.QRectF(0, 0, 100, 100), 'in scene')
self._scene.addItem(scene_item)
group_item = GraphicsItem(QtCore.QRectF(150, 0, 100, 100), 'in group')
group.addToGroup(group_item)
group_item = GraphicsItem(QtCore.QRectF(300, 0, 100, 100), '2nd in group')
group.addToGroup(group_item)
self.setCentralWidget(self._widget)
self.setWindowTitle('contextMenuEvent with QGraphicsItemGroup')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.setGeometry(100, 100, 800, 500)
mainWindow.show()
sys.exit(app.exec_())