"Pixmap" migration on Gtk 3 - python

I have a python 2 project using GTK2.
I am working on this project to migrate python 2 to python 3 and Gtk2 to Gtk 3.
I have a problem with the Gtk migration.
I want to replace “gdk. Pixmap” in my code.
I found this documentation:
Replace GdkPixmap by cairo surfaces The GdkPixmap object and related
functions have been removed. In the Cairo-centric world of GTK 3,
Cairo surfaces take over the role of pixmaps.
I have to use Cairo, but I don’t know how.
I spent a lot of time looking for examples in python. I didn’t find anything that matched my code.
Can someone help me, give me references?
Python2 :
class TraceView(gtk.DrawingArea):
…
def configure_event(self, widget, event):
_, _, width, height = widget.get_allocation()
self.pixmap = Pixmap(widget.window, width, height)
self.pixmap.draw_rectangle(widget.get_style().white_gc, True, 0, 0, width, height)
...
return True
def expose_event(self, widget, event):
x, y, width, height = event.area
widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL],
self.pixmap, x, y, x, y, width, height)
self.maj_exposed()
...
return False
Python3 :
class TraceView(Gtk.DrawingArea):
…
def configure_event(self, widget, event):
width = widget.get_allocated_width()
height = widget.get_allocated_height()
self.pixmap = ?
self.pixmap. … ?
...
return True
def draw(self, widget, event):
  ???
self.maj_exposed()
...
return False

This is a minimal example of gtk3 program that draws a rectangle
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class MinimalCairoTest(Gtk.Window):
def __init__(self):
super(MinimalCairoTest, self).__init__()
self.set_size_request(400, 400)
self.connect("destroy", Gtk.main_quit)
darea = Gtk.DrawingArea()
darea.connect("draw", self.__draw_cb)
self.add(darea)
self.show_all()
def __draw_cb(self, widget, cairo_context):
cairo_context.set_source_rgb(1.0, 0.0, 0.0)
cairo_context.rectangle(20, 20, 120, 80)
cairo_context.fill()
MinimalCairoTest()
Gtk.main()
You can find more examples about how to draw with cairo here https://seriot.ch/pycairo/ and documentation here https://pycairo.readthedocs.io/en/latest/reference/context.html

Related

How to draw a figure on mouse click in qtapplication

I'm new to Pyqt5 and writing applications with it in Python so forgive me if this is a very simple question but I'm having trouble drawing ellipses in my program. I want to draw one by wherever a click occurs. Here is my code:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
class Window(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = QGraphicsScene()
self.setScene(self.scene)
# p.setTransform(transform)
self.button = QPushButton("Draw")
self.button.setCheckable(True)
self.button.setGeometry(0, 0, 100, 30)
self.scene.addWidget(self.button)
# self.setMouseTracking(True)
width, height = 1000, 1000
self.setFixedSize(width, height);
self.setSceneRect(0, 0, width, height);
self.fitInView(0, 0, width, height, Qt.KeepAspectRatio);
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.show()
def mousePressEvent(self, event):
if self.button.isChecked():
x = event.x()
y = event.y()
print(x, y)
ellipse = QGraphicsEllipseItem(x, y, 50, 20)
self.scene.addItem(ellipse)
The issue I'm having is I think the mousePressEvent function isn't allowing me to click on my button to enable drawing but the part I'm really not sure about is what is going on in the mousePressEvent. It seems as though it's getting the (x, y) coordinates within the QGraphicsView object but my ellipses are getting drawn in strange spots far away from wherever is clicked in my application when it's open.
You should not override the mousePressEvent as you remove the default behavior such as sending the event to the button. On the other hand you have to convert the coordinates of the view to the coordinates of the scene.
self.proxy_widget = self.scene.addWidget(self.button)
def mousePressEvent(self, event):
super().mousePressEvent(event)
vp = event.pos()
if self.proxy_widget in self.items(vp):
return
if self.button.isChecked():
ellipse = QGraphicsEllipseItem(0, 0, 50, 20)
self.scene.addItem(ellipse)
sp = self.mapToScene(vp)
ellipse.setPos(sp)

How to force screen-snip size ratio. PyQt5

I want to modify Screen-Snip code from GitHub/harupy/snipping-tool so that every screen-snip has a ratio of 3 x 2. (I will save as 600 x 400 px image later)
I'm not sure how to modify self.end dynamically so that the user clicks and drags with a 3 x 2 ratio. The mouse position will define the x coordinate, and the y coordinate will be int(x * 2/3)
Any suggestions on how to do this? I promise I've been researching this, and I just can't seem to "crack the code" of modifying only the y coordinate of self.end
Here is the code:
import sys
import PyQt5
from PyQt5 import QtWidgets, QtCore, QtGui
import tkinter as tk
from PIL import ImageGrab
import numpy as np
import cv2 # package is officially called opencv-python
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
root = tk.Tk()
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
self.setGeometry(0, 0, screen_width, screen_height)
self.setWindowTitle(' ')
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.setWindowOpacity(0.3)
QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.CrossCursor)
)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
print('Capture the screen...')
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setPen(QtGui.QPen(QtGui.QColor('black'), 3))
qp.setBrush(QtGui.QColor(128, 128, 255, 128))
qp.drawRect(QtCore.QRect(self.begin, self.end)) ##### This seems like the place I should modify. #########
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = self.begin
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.close()
x1 = min(self.begin.x(), self.end.x())
y1 = min(self.begin.y(), self.end.y())
x2 = max(self.begin.x(), self.end.x())
y2 = max(self.begin.y(), self.end.y())
img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
img.save('capture.png')
img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)
cv2.imshow('Captured Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyWidget()
window.show()
app.aboutToQuit.connect(app.deleteLater)
sys.exit(app.exec_())
You don't need to "change the y coordinate", you just need to use the correct arguments to create the rectangle.
There are various ways to initialize a QRect, you are using the two points, another one (and more common) is to use the coordinates of the origin and the size of the rectangle.
Once you know the width, you can compute the height, and make it negative if the y of the end point is above the begin.
Note that in this way you could get a "negative" rectangle (negative width, with the "right" edge actually at the left, the same for the height/bottom), so it's usually better to use normalized, which also allows you to get the correct coordinates of the rectangle for screen grabbing.
class MyWidget(QtWidgets.QWidget):
# ...
def getRect(self):
# a commodity function that always return a correctly sized
# rectangle, with normalized coordinates
width = self.end.x() - self.begin.x()
height = abs(width * 2 / 3)
if self.end.y() < self.begin.y():
height *= -1
return QtCore.QRect(self.begin.x(), self.begin.y(),
width, height).normalized()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setPen(QtGui.QPen(QtGui.QColor('black'), 3))
qp.setBrush(QtGui.QColor(128, 128, 255, 128))
qp.drawRect(self.getRect())
def mouseReleaseEvent(self, event):
self.close()
rect = self.getRect()
img = ImageGrab.grab(bbox=(
rect.topLeft().x(),
rect.topLeft().y(),
rect.bottomRight().x(),
rect.bottomRight().y()
))
# ...
I suggest you to use a delayed setGeometry as in some systems (specifically Linux), the "final" geometry is actually applied only as soon as the window is correctly mapped from the window manager, especially if the window manager tends to apply a geometry on its own when the window is shown the first time. For example, I have two screens, and your window got "centered" on my main screen, making it shifted by half width of the other screen.
Also consider that importing Tk just for the screen size doesn't make much sense, since Qt already provides all necessary tools.
You can use something like that:
class MyWidget(QtWidgets.QWidget):
# ...
def showEvent(self, event):
if not event.spontaneous():
# delay the geometry on the "next" cycle of the Qt event loop;
# this should take care of positioning issues for systems that
# try to move newly created windows on their own
QtCore.QTimer.singleShot(0, self.resetPos)
def resetPos(self):
rect = QtCore.QRect()
# create a rectangle that is the sum of the geometries of all available
# screens; the |= operator acts as `rect = rect.united(screen.geometry())`
for screen in QtWidgets.QApplication.screens():
rect |= screen.geometry()
self.setGeometry(rect)

GTK3+ (3.14) drawing area on scrolledwindow

It makes a while that I've been trying to put a drawing area on a scrolled window. I've been reading articles about pygtk and C solutions but I think that they are not working in pyGobject.
I made a minimal example:
from gi.repository import Gtk, Gdk
import cairo
class Test(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
sw=Gtk.ScrolledWindow()
vp=Gtk.Viewport()
box=Gtk.VBox()
vp.set_size_request(100,100)
for i in range(3):
da=Gtk.DrawingArea()
da.connect("draw", self.draw, [0.3, 0.4, 0.6], da)
da.set_size_request(100,100)
box.add(da)
sw.add(vp)
vp.add(box)
self.add(sw)
self.show_all()
def draw(self, widget, event, color, da):
cr = widget.get_property('window').cairo_create()
cr.rectangle(0, 0, 100, 100)
cr.set_source_rgb(color[0], color[1], color[2])
cr.fill()
main=Test()
Gtk.main()
So the problem is that the drawing areas are not always rendered. This is for example, a gtk2 working code:
import gtk, cairo
class Test(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
sw=gtk.ScrolledWindow()
vp=gtk.Viewport()
box=gtk.VBox()
for i in range(3):
da=gtk.DrawingArea()
da.connect("expose-event", self.draw, [0.3, 0.4, 0.6], da)
box.add(da)
sw.add(vp)
vp.add(box)
self.add(sw)
self.show_all()
def draw(self, widget, event, color, da):
cr = widget.get_property('window').cairo_create()
cr.rectangle(0, 0, 100, 100)
cr.set_source_rgb(color[0], color[1], color[2])
cr.fill()
main=Test()
gtk.main()
Please do not point me to the following articles, I've already read them multiple times!
Gtk Forum: 1652
SO
AskUbuntu
pyGtk faq
I've added the viewport and a size_request, what else could be missing?
Thanks for the help!
by Emmanuele over the Gtk mailing list:
from gi.repository import Gtk, Gdk
import cairo
class Test(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
sw=Gtk.ScrolledWindow()
vp=Gtk.Viewport()
box=Gtk.VBox()
vp.set_size_request(100,100)
for i in range(3):
da=Gtk.DrawingArea()
da.connect("draw", self.draw, [0.3, 0.4, 0.6])
da.set_size_request(100,100)
box.add(da)
sw.add(vp)
vp.add(box)
self.add(sw)
self.show_all()
def draw(self, widget, cr, color):
cr.rectangle(0, 0, 100, 100)
cr.set_source_rgb(color[0], color[1], color[2])
cr.fill()
cr.queue_draw_area(0, 0, 100, 100)
return True
main=Test()
Gtk.main()
You should read the API reference for GTK+ 3.x:
https://developer.gnome.org/gtk/stable
as well as the Python API reference:
http://lazka.github.io/pgi-docs/#Gtk-3.0
You can add a damage region and force the redraw, I've slightly modified you example (sorry I could not resist fixing a couple of things) and add the queue_draw_area
I would strongly suggest avoid using a Gtk.DrawingArea and using a canvas widget instead, drawing on a canvas it's just much easier, GooCanvas is a good example but there are many others that you can use.
from gi.repository import Gtk, Gdk
import math, cairo
class Test(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
sw=Gtk.ScrolledWindow()
box=Gtk.VBox()
for i in range(3):
da=Gtk.DrawingArea()
da.connect("draw", self.draw, [0.3, 0.4, 0.6], da)
da.set_size_request(100,100)
box.pack_start(da, True, True, 10)
sw.add(box)
self.add(sw)
self.connect("destroy", Gtk.main_quit)
self.show_all()
def draw(self, widget, event, color, da):
cr = widget.get_property('window').cairo_create()
lg1 = cairo.LinearGradient(0.0, 0.0, 100, 0)
lg1.add_color_stop_rgb(0, color[0], color[1], color[2])
lg1.add_color_stop_rgb(1, color[0], color[1], color[2])
cr.rectangle(0, 0, 100, 100)
cr.set_source(lg1)
cr.fill()
da.queue_draw_area(0, 0, 100, 100)
main=Test()
Gtk.main()

GTK: How can the size of a TextView be set manually?

I am reworking a GUI from scratch using Glade + Python. It was previously done in Tkinter and I could set the height and width of several consoles.
Now, in GTK (newbie) I can't find a proper solution to this: specify a size to the TextView. I imagine it is done by nesting it in a block that sets the size. What's my best option?
Here's my case: http://pastebin.com/dx07HZnC
And a preview: http://www.tiikoni.com/tis/view/?id=24e83d2
As you can see the textView widgets are very short.
PyGTK2
textview = gtk.TextView()
textview.set_size_request(300,200) # W x H
textview.show()
window.add(textview)
I can't see your case, but I think that I can help you:
import pygtk
pygtk.require('2.0')
import gtk
import pango
class MyGUI:
def __init__( self, title):
self.window = gtk.Window()
self.title = title
self.window.set_title("Title")
self.window.set_size_request(260, 300) ***where 260 is Width and 300 is Height***
self.window.connect( "destroy", self.destroy)
self.create_interior()
self.window.show_all()
def create_interior( self):
self.mainbox = gtk.VBox()
self.window.add( self.mainbox)
# the textview
self.textview = gtk.TextView()
self.textbuffer = self.textview.get_buffer()
self.mainbox.pack_start( self.textview)
self.textview.show()
h_tag = self.textbuffer.create_tag( "h", size_points=16, weight=10)
position = self.textbuffer.get_end_iter()
self.textbuffer.insert( position, "Text...")
self.mainbox.show()
def main( self):
gtk.main()
def destroy( self, w):
gtk.main_quit()
if __name__ == "__main__":
m = MyGUI( "TextView example.")
m.main()

Python GTK - DrawingArea with opacity

I'm working on a transparent window which includes a DrawingArea widget, which gets on top with a solid background color, but I want to keep it transparent. I've tried everything, unfortunately the docs are a bit dated (with PyGTK on the top results). Other things I've done is connecting its draw event as I'm doing it with the window, but with no success.
Of course I need the square to be shown, so I just need the background color to transparent. I've also tried with modify_bg, but I only manage to set it to solid colors.
Here's what I have so far.
#!/usr/bin/env python
from gi.repository import Gtk, Gdk
import cairo
class GWin (Gtk.Window):
def __init__(self):
super(GWin, self).__init__()
self.set_position(Gtk.WindowPosition.CENTER)
self.screen = self.get_screen()
self.visual = self.screen.get_rgba_visual()
if self.visual != None and self.screen.is_composited():
self.set_visual(self.visual)
self.connect("draw", self.on_win_draw)
self.set_app_paintable(True)
self.show_all()
self.draw_area = Gtk.DrawingArea()
self.draw_area.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.draw_area.connect('draw', self.begin_draw)
self.draw_area.show()
self.add(self.draw_area)
def begin_draw(self, draw_area, cairo_context):
cairo_context.rectangle(20, 20, 120, 120)
cairo_context.stroke()
def on_win_draw(self, widget, cr):
cr.set_source_rgba(1, 1, 1, 0.1)
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
win = GWin()
win.connect('delete-event', Gtk.main_quit)
Gtk.main()
You need to set the cairo.Context source rgba before creating the rectangle. Here is the code, the edited line marked with ### ADDED THIS LINE:
#!/usr/bin/env python
from gi.repository import Gtk, Gdk
import cairo
class GWin (Gtk.Window):
def __init__(self):
super(GWin, self).__init__()
self.set_position(Gtk.WindowPosition.CENTER)
self.screen = self.get_screen()
self.visual = self.screen.get_rgba_visual()
if self.visual != None and self.screen.is_composited():
self.set_visual(self.visual)
self.connect("draw", self.on_win_draw)
self.set_app_paintable(True)
self.show_all()
self.draw_area = Gtk.DrawingArea()
self.draw_area.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.draw_area.connect('draw', self.begin_draw)
self.draw_area.show()
self.add(self.draw_area)
def begin_draw(self, draw_area, cairo_context):
cairo_context.set_source_rgba(1, 1, 1, 1) ### ADDED THIS LINE
cairo_context.rectangle(20, 20, 120, 120)
cairo_context.stroke()
def on_win_draw(self, widget, cr):
cr.set_source_rgba(1, 1, 1, 0.1)
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
win = GWin()
win.connect('delete-event', Gtk.main_quit)
Gtk.main()

Categories