I have trouble rendering a custom GTK3 drawing area with custom cairo code.
It seems that the cairo context doesn't want to trouble with the shape I specify. It writes the whole source onto the whole destination, regardless of the shape.
So for example, if I set a fully white source area to paint a small white rectangle, and I ask cairo to fill() that rectangle, it paints the whole DrawingArea widget instead of that rectangle. What am I missing here ?
#!/usr/bin/env python3
from gi.repository import Gtk as gtk
from gi.repository import Gdk as gdk
import cairo
class Canvas(gtk.DrawingArea):
def __init__(self):
gtk.DrawingArea.__init__(self)
self.set_size_request(600,400)
self.connect('draw',Canvas.on_draw)
def on_draw(self,context):
context.set_source_rgb( 0.1, 0.0, 0.1 )
context.fill()
context.set_source_rgb( 1.0, 1.0, 1.0 )
context.rectangle(50,50,100,100)
context.fill()
context.paint()
return False
#look at this code
class MainWindow(gtk.Window):
def __init__(self):
gtk.Window.__init__(self, title="Game Centurion")
self.dummy0 = gtk.Label(label="dummy₀")
self.canvas = Canvas()
self.box = gtk.Box()
self.box.pack_start(self.canvas, True, True, 0)
self.box.pack_start(self.dummy0, True, True, 0)
self.add(self.box)
self.connect('delete-event', gtk.main_quit)
if __name__ == '__main__':
w = MainWindow()
w.show_all()
gtk.main()
Here is the result window, you can see it is fully white instead of having a small white square over a dark purple background as I specified in the code above.
Thank you for any help.
Okay, I got it.
I misunderstood the role of the method paint of object context.
I though it was some kind of "commit" to actually perform all operation specified before.
Actually those operations are painted "on spot" (or after the return call, I dunno), and paint is an operation that copy the whole source into the destination.
No wonder it didn't work. out so well.
Here is the correct drawing callback :
def on_draw(self,emitter,context):
context.set_source_rgb( 0.1, 0.0, 0.1 )
context.paint()
context.rectangle(50,50,100,100)
context.set_source_rgb( 1.0, 1.0, 1.0 )
context.fill()
return False
Related
I have an implementation of k-NN algorithm in Python, that returns the class label of an input. What I need is to show an image assigned to the class label in one window while output is coming (by refreshing the window). Problem is, I am not very experienced in GUI programming, thus I need some resources and assistance to start with. What libraries, books and tutorials could you recommend? Pieces of code would be appreciated too.
There are many libraries allow you add images to your program such as Turtle, PIL.ImagTk and Canvas and you can even use Pygame to a best performance.
PIL.ImagTk and Canvas:
from Tkinter import *
from PIL import ImageTk
backgroundImage = PhotoImage("image path (gif/PPM)")
canvas = Canvas(width = 200, height = 200, bg = 'blue')
canvas.pack(expand = YES, fill = BOTH)
image = ImageTk.PhotoImage(file = "C:/Python27/programas/zimages/gato.png")
canvas.create_image(10, 10, image = image, anchor = NW)
mainloop()
Turtle:
import turtle
screen = turtle.Screen()
# click the image icon in the top right of the code window to see
# which images are available in this trinket
image = "rocketship.png"
# add the shape first then set the turtle shape
screen.addshape(image)
turtle.shape(image)
screen.bgcolor("lightblue")
move_speed = 10
turn_speed = 10
# these defs control the movement of our "turtle"
def forward():
turtle.forward(move_speed)
def backward():
turtle.backward(move_speed)
def left():
turtle.left(turn_speed)
def right():
turtle.right(turn_speed)
turtle.penup()
turtle.speed(0)
turtle.home()
# now associate the defs from above with certain keyboard events
screen.onkey(forward, "Up")
screen.onkey(backward, "Down")
screen.onkey(left, "Left")
screen.onkey(right, "Right")
screen.listen()
Now let’s change the background
import turtle
screen = turtle.Screen()
# this assures that the size of the screen will always be 400x400 ...
screen.setup(400, 400)
# ... which is the same size as our image
# now set the background to our space image
screen.bgpic("space.jpg")
# Or, set the shape of a turtle
screen.addshape("rocketship.png")
turtle.shape("rocketship.png")
move_speed = 10
turn_speed = 10
# these defs control the movement of our "turtle"
def forward():
turtle.forward(move_speed)
def backward():
turtle.backward(move_speed)
def left():
turtle.left(turn_speed)
def right():
turtle.right(turn_speed)
turtle.penup()
turtle.speed(0)
turtle.home()
# now associate the defs from above with certain keyboard events
screen.onkey(forward, "Up")
screen.onkey(backward, "Down")
screen.onkey(left, "Left")
screen.onkey(right, "Right")
screen.listen()
for Turtle:
http://blog.trinket.io/using-images-in-turtle-programs/
I'm trying to draw a path on a QGraphicsView. However, the position seems not right. The first point(red) is (0,0), which is supposed to be at the top-left corner. How do I move the drawing to the right position?
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import QPointF as qpf
import sys
from PyQt4.QtGui import QPainterPath
data= [qpf(0,0),qpf(40,30),qpf(30,60),qpf(70,90),qpf(20,120),qpf(60,150)]
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.view = View(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.view)
class View(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
item = QtGui.QGraphicsRectItem(data[0].x()-2,data[0].y()-2,4,4)
item.setBrush(QtCore.Qt.red)
self.scene().addItem(item)
self.path = path = QPainterPath(data[0])
for d in data[1:]:
path.lineTo(d)
item = QtGui.QGraphicsPathItem(path)
self.scene().addItem(item)
def mouseReleaseEvent(self, event):
pos = event.pos()
rect = QtCore.QRectF(pos.x()-2, pos.y()-2,4,4)
item = QtGui.QGraphicsRectItem(rect)
self.scene().addItem(item)
if self.path.intersects(rect):
print 'on line'
else:
print 'Not belong to line (%d, %d)' % (pos.x(), pos.y())
QtGui.QGraphicsView.mouseReleaseEvent(self, event)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(400, 400)
window.show()
sys.exit(app.exec_())
This behavior is because you draw using QGraphicsScene and QGraphicsView. This is because view and scene have some automatism, which are very convenient, normally. The scene coordinated can be completely different from the view (which is in pixels).
From the docs: "After you call show(), the view will by default scroll to the center of the scene and display any items that are visible at this point." show() is called implicitly in your case, I believe after you add an item to the scene.
I can think of two possibilities to get what you want:
1) change the view onto your scene, so that the scene-coordinate (0, 0) is in the upper left corner of your view.
2) Do NOT use QGraphicsScene and QGraphicsView, but just draw on a widget from its paint event as shown for example here. This means your dimensions are all in pixels, i.e. the coordinates of your points are pixels. And no automatism that might confuse you is done.
You would want to use self.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) in your view class constructor, that will make 0,0 of the scene coordinates the same as 0,0 in view coordinates.AlignmentFlag
I would also suggest pos = self.mapToScene(event.pos()) in the mouseReleaseEvent. that way if you zoom in or scroll the scene, the click will happen in the right place in the view. QGraphicsView.mapFromScene
I've been playing with this code and I've found some (probably) strange thing: when I add parent to my class, size changes to [100, 100] (see comments):
from random import random, randint
import kivy
kivy.require('1.8.0')
from kivy.config import Config
Config.set('graphics', 'fullscreen', '0')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Line, Ellipse, Triangle, Rectangle
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
with self.canvas:
Color(random(), 1, 1, mode='hsv')
touch.ud['line'] = Line(points=(self.width - touch.x, self.height - touch.y))
print(self.width, self.height) # It works OK if I return painter below, but it's not if I return parent.
def on_touch_move(self, touch):
touch.ud['line'].points += [self.width - touch.x, self.height - touch.y]
class Example(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
print(painter.size) # Shows [100, 100] anyway.
parent.add_widget(painter)
return parent # If I return painter, everything works as I want (except painter.size still shows [100, 100]).
if __name__ == '__main__':
Example().run()
Why does that happen? And how should I make it right?
(100, 100) is the default size for widgets. In this case your painter has that size because you never set it to anything else.
Even if its parent were a layout class that would automatically move and resize the painter, its size would still read (100, 100) at this point because the layout hasn't had time to run yet. You shouldn't generally worry about the pixel values at this point - if you need something else to depend on them, use a binding to update the other thing automatically when the first one changes. This is made particularly easy by kv language.
I hope to make a QGLWidget that has a transparent background, like a figure above.
It's a cpp code and I don't understand the some parts of it. As far as I know, it uses window handle, drawing context, and so on. But I'm poor at C and C++. I've used Python and PyQt, so it seems like a Atlantis for me. Is there any idea for QGLWidget that has a transparent background?
ADD
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QApplication, QColor
from PyQt4.QtOpenGL import QGLWidget
from OpenGL import GL
import sys
class TransGLWidget(QGLWidget):
def __init__(self, parent=None):
QGLWidget.__init__(self, parent)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setGeometry(200, 100, 640, 480)
def initializeGL(self):
self.qglClearColor(QColor(0, 0, 0, 0))
def resizeGL(self, w, h):
GL.glViewport(0, 0, w, h)
GL.glMatrixMode(GL.GL_PROJECTION)
GL.glLoadIdentity()
x = float(w) / h
GL.glFrustum(-x, x, -1.0, 1.0, 1.0, 10.0)
GL.glMatrixMode(GL.GL_MODELVIEW)
GL.glLoadIdentity()
def paintGL(self):
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
GL.glBegin(GL.GL_TRIANGLES)
GL.glColor3f(1.0, 0.0, 0.0)
GL.glVertex3f(-1.0, 1.0, -3.0)
GL.glColor3f(0.0, 1.0, 0.0)
GL.glVertex3f(1.0, 1.0, -3.0)
GL.glColor3f(0.0, 0.0, 1.0)
GL.glVertex3f(0.0, -1.0, -3.0)
GL.glEnd()
app = QApplication(sys.argv)
widget = TransGLWidget()
widget.show()
app.exec_()
I try with above code, but it shows me nothing. (It seems a whole transparent widget.) If I get rid of "setAttribute", "setWindowFlags", is shows up a triangle. Is there any miss at the code?
Translated into Python from this C++ solution. The semicolon at the end has been removed, :: has been replaced with ., and True has been capitalized. The main source of difficulty is that setAttribute and setWindowFlags are actually methods of the QGLWidget, so self. has been added. Just add this in the __init__() constructor of your QGLWidget`.
self.setAttribute(Qt.WA_TranslucentBackground, True)
You can add
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
if you want the window to be on top of others and have no frame.
I use QSplitter and I found out that the minumum width of a widget in
the splitter is 32 pixels (and 23 pixels in height). Does anybody body knows how
to change this default. In other words, you can't drag the splitter so that one of the
widgets (assume that there are 2 widgets in the spllitter) in the spllitter will be less
than 32 pixels in width.
The code:
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.resize(400,400)
m = QtGui.QSplitter(self)
m.resize(200, 100)
x = QtGui.QPushButton(m)
x.setGeometry(0, 0, 100, 100)
y = QtGui.QPushButton(m)
y.setGeometry(0, 100, 100, 100)
m.setSizes([20, 180])
# this will show you that the width of x is 32 (it should be 20!)
print x.width()
Note: I'm using Python 3.6.2 and PyQt5, though the logic in the example stays the same and can be understood even if you're using other versions of Python and PyQt.
Look at what is said here:
If you specify a size of 0, the widget will be invisible. The size policies of the widgets are preserved. That is, a value smaller than the minimal size hint of the respective widget will be replaced by the value of the hint.
One of the options to solve your problem is to call x.setMinimumWidth() with a small value, like:
x.setMinimumWidth(1)
However, if you'll try it yourself, you'll see that
it is a dirty hack as it actually leaves the widget here, just makes it very narrow and
though now you can drag the splitter, the initial width of the widget is still "32" instead of "20".
x.setMinimumWidth(0)
also doesn't work as expected: its minimal width is actually zero by default (as this widget has no contents, I guess), but it doesn't help you to make splitter item less than 32 pixels wide unless you collapse it.
By the way, set
m.setCollapsible(0, False)
m.setCollapsible(1, False)
if you want splitter to stop collapsing its two children widgets. More details here.
The solution I've found is to overload sizeHint() method of the widget you want to include into the splitter, as in example below (look at the ButtonWrapper class and what is output like now).
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#Python 3.6.2 and PyQt5 are used in this example
from PyQt5.QtWidgets import (
QPushButton,
QSplitter,
QWidget,
QApplication,
)
import sys
class ButtonWrapper(QPushButton):
def sizeHint(self):
return self.minimumSize()
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(400, 400)
m = QSplitter(self)
m.resize(200, 100)
x = ButtonWrapper(self)
x.setGeometry(0, 0, 100, 100)
y = QPushButton(self)
y.setGeometry(0, 100, 100, 100)
m.addWidget(x)
m.addWidget(y)
m.setSizes([20, 180])
#Now it really shows "20" as expected
print(x.width())
#minimumWidth() is zero by default for empty QPushButton
print(x.minimumWidth())
#Result of our overloaded sizeHint() method
print(x.sizeHint().width())
print(x.minimumSizeHint().width())
self.setWindowTitle('Example')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
I'm not sure if this is the right way to do stuff, but I've spent lots of time trying to solve my own problem connected to this, and haven't seen anything satisfying yet so far. I'll really appreciate it if someone knows a better actually working & clear workaround.