How to Screenshot a Website Element without Collapsible Division using Selenium? - selenium

I want to screenshot just the US hot pots map in https://www.nytimes.com/interactive/2021/us/covid-cases.html but the collapsible division at the very bottom (that says Thanks for reading the Times) keeps coming with the screenshot:
How can I exclude that?
Also ideally the New York Time banner at the top would be cropped out. I used Pillow's Image.crop() to crop from the first image captured but wonder if there is a more convenient/elegant way to achieve that. Any thoughts? Thank you!
Here's my code:
from Screenshot import Screenshot
from selenium.webdriver.common.by import By
from selenium import webdriver
from PIL import Image
ob = Screenshot.Screenshot()
driver = webdriver.Chrome()
driver.page_load_strategy = 'none'
url = "https://www.nytimes.com/interactive/2021/us/covid-cases.html"
driver.get(url)
class_name = "mapboxgl-canvas"
element = driver.find_element(By.CLASS_NAME, class_name)
element.screenshot('{}.png'.format(class_name))
location = element.location
size = element.size
print(class_name, 'location:', location, 'size:', size, '\n')
location = element.location
size = element.size
x = location['x']
# y = location['y']
y = 30
w = x + size['width']
h = y + size['height']
# x = 0; y = 10; w = 950; h = 600
fullImg = Image.open("mapboxgl-canvas.png")
cropImg = fullImg.crop((x, y, w, h))
cropImg.save('cropImage.png')
driver.close()
driver.quit()

After tons of trials and errors, finally I got my code to work. The key is to suppress the expandable dock and re-position the capturing window in js.
Attached is the map with top banner and bottom mapbox logo cropped out.
import platform
from Screenshot import Screenshot
from selenium.webdriver.common.by import By
from PIL import Image, ImageDraw
from selenium import webdriver
from selenium.webdriver import ChromeOptions
import numpy as np
#-------------------------------- Part 1 Take a Screenshot of the Map -----------------------------------
#Set up Chrome driver
options = ChromeOptions()
options.headless = True
options.add_argument('--page_load_strategy = none') #suppress browser window
driver = webdriver.Chrome(executable_path = ('/usr/local/bin/' if platform.system() == 'Linux' else '' + 'chromedriver')
, options=options)
# driver.get_window_rect()
_ = driver.set_window_rect(x=0, y=50, width=1000, height=1000)
# driver.execute_script("document.body.style.zoom='145%'")
#Load the website to capture screenshot
url = "https://www.nytimes.com/interactive/2021/us/covid-cases.html"
driver.get(url)
driver.execute_script("""window.scrollTo(0, 2071);
var element = document.querySelector('[data-testid="expanded-dock"]');
element.style.visibility = 'collapse';
element.style.height = '0px';""") #Suppress the randomly pop-out expandable dock at the bottom which messes up the screenshot window
#Identify the element to screenshot, which is the hot spots map
#-------------------------------------------------------------------------------------------------------
#Can't use class_name = "mapboxgl-canary",'multi-map', "map-wrap", "mapboxgl-interactive"
#These class_names are the same as "mapboxgl-canvas": "aspect-ratio-outer", "aspect-ratio-inner", "map", "mapboxgl-map"
#Using ID = "maps" works too, just y location is different. Stick to class_name = "mapboxgl-canvas"
# tag = "maps"
# element = driver.find_element(By.ID, tag)
#-------------------------------------------------------------------------------------------------------
tag = "mapboxgl-canvas"
element = driver.find_element(By.CLASS_NAME, tag)
img_path = '{}.png'.format(tag)
_ = element.screenshot(img_path)
#Check map location and size for window.scrollTo coordinates
location = element.location
size = element.size
print(tag, 'location:', location, 'size:', size, '\n')
# Make sure window.scrollTo = y location = 2382 and height and width stay at 643 and 919 when configuring set_window_rect()
# mapboxgl-canvas location: {'x': 30, 'y': 2382} size: {'height': 643, 'width': 919}
#Crop image to remove unwanted pixels
# x = location['x']
# y = location['y']
x=0; y=30 #Start from a lower position to crop the top banner
w = x + size['width']
h = y + size['height'] - 60 #Subtract from height to remove mapbox logo at the bottom
fullImg = Image.open(img_path)
cropImg = fullImg.crop((x, y, w, h)) #(left, upper, right, lower)-tuple
cropImg.save('cropImage.png')
fullImg.close()
driver.close()
driver.quit()
#--------- Part 2 Mask unwanted parts of the image (top right size control, P.R. region at bottom right) -----------
im = Image.open('cropImage.png')
draw = ImageDraw.Draw(im)
#Vertices of masking rectangles, one for top right size control, the other for bottom right Puerto Rico
top_left = (cropImg.width -41, 0)
bottom_right = (cropImg.width - 8, 40)
top_left2 = (cropImg.width - 100, cropImg.height - 45)
bottom_right2 = (cropImg.width - 40, cropImg.height - 15)
draw.rectangle((top_left, bottom_right), fill=(255, 255, 255))
draw.rectangle((top_left2, bottom_right2), fill=(255, 255, 255))
# Save final image
im.save('cropImage1.png')

Related

Saving QBarChart as SVG, Y-axis is not proper drawn

I create multiple QChart() with QLineSeries() and QBarSeries. I like to save the files as pngand svg. The funtion to make the graphs works, but saving the files as svg is different than png.
Result
Code to show fault
`
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
from PyQt5.QtChart import QChart, QChartView, QBarSet, QPercentBarSeries, QBarCategoryAxis, QValueAxis, QBarSeries
from PyQt5.QtGui import QPainter, QFont
from PyQt5.QtCore import Qt, QRectF, QPointF, QSizeF, QSize
from PyQt5.QtSvg import QSvgGenerator
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PyQt BarChart")
self.setGeometry(100,100, 680,500)
self.show ()
barDict = {'TotalRMSV[mm/s]': [2.85], 'barnames': ['2022']}
chartview = QChartView()
output_size = QSize (800, 600) # pixels
chart = barChart(barDict, "Test")
chartview.setChart(chart)
self.setCentralWidget (chartview)
save_chart_as_svg(chartview, output_size, "c://temp/test.svg")
def barChart(barDict, graphtitle='barChart'):
print ("-> qt_graph_tools.barChart")
series = chart_create_bar_series(barDict)
axisY = QValueAxis ()
axisY.setMin(1) # If I set Min to 0, all graphics are ok.
axisY.setMax(4)
chart = QChart ()
chart.addSeries (series)
chart.setAxisY (axisY, series)
chart.setTitle (graphtitle)
chart.setAnimationOptions (QChart.NoAnimation)
chart.legend ().setAlignment (Qt.AlignBottom)
return chart
def save_chart_as_svg(chartView, output_size, save_as):
# the desired size of the rendering
# in pixels for PNG, in pt for SVG
output_size.setWidth (int (output_size.width () * 0.75))
output_size.setHeight (int (output_size.height () * 0.75)) # tranlate pixels to pt
output_rect = QRectF (QPointF (0, 0), QSizeF (output_size)) # cast to float
chart = chartView.chart ()
svg = QSvgGenerator()
svg.setFileName (save_as)
svg.setSize (output_size)
svg.setViewBox (output_rect)
chart.resize (output_rect.size ())
painter = QPainter ()
painter.begin (svg)
painter.setRenderHint (QPainter.Antialiasing)
chart.scene ().render (painter, source=output_rect, target=output_rect, mode=Qt.IgnoreAspectRatio)
painter.end ()
return svg
def chart_create_bar_series( bardict):
print("-> qt_graph_tools.chart_create_bar_series")
series = QBarSeries ()
for key, data_set in bardict.items():
if key != 'gear' and key != 'barnames' and key != 'colors':
barset = QBarSet(key)
barset.append(data_set)
series.append(barset)
return series
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec_())
`
The image is the resulting SVG shown in Firefox and the Window is its data as QChartView. System: Windows 11, PyQt5.15, python 3.10. If you set the axisY.setMin(0), the output is correct, with higher value it should be cutted at the borderline, but is not. It's not a good idea to set Min fix to 0, because sometimes I'm interested in a range e.g. 5..7

multiple processing in dronekit not working

i am trying to make a code about a drone flying to multiple waypoint and the drone can't continue to the next waypoint when i not showing the red color on camera.
because the camera cv2 and the drone runs at the same time, my code runs very laggy, so i tried using multiprocessing method and modify my code. when i trying to run my new code, my multi processing doesn't work and it keeps skipping almost of my code and straight to RTL mode.
from inspect import ArgInfo
from dronekit import connect, VehicleMode, LocationGlobalRelative
from pymavlink import mavutil
from numpy import loadtxt, array
from time import sleep
import sys
import cv2
import numpy as np
import multiprocessing
cap = cv2.VideoCapture(0)
hsv_a = np.array([198, 255, 255])
hsv_b = np.array([158, 68, 137])
treshold = 150
lat = [-35.3629722, -35.3629064, -35.3634361, -35.3638474]
lon = [149.1649709, 149.1655721, 149.1657331, 149.1639733]
#vehicle = connect('udp:127.0.0.1:14551',wait_ready=True)
vehicle = connect('udp:127.0.0.1:14551',wait_ready=True)
def arm_and_takeoff(aTargetAltitude): #fungsi arming dan takeoff
print("Basic pre-arm checks")
# Don't let the user try to arm until autopilot is ready
while not(vehicle.is_armable):
print(" Waiting for vehicle to initialise...")
sleep(1)
print("Arming motors")
# Copter should arm in GUIDED mode
vehicle.mode = VehicleMode("GUIDED")
vehicle.armed = True
while not(vehicle.armed):
print(" Waiting for arming...")
sleep(1)
print("Taking off!")
vehicle.simple_takeoff(aTargetAltitude)
while True:
print(" Altitude: ", vehicle.location.global_relative_frame.alt)
#Break and return from function just below target altitude.
if (vehicle.location.global_relative_frame.alt>=aTargetAltitude*0.95):
print("Reached target altitude")
break
sleep(1)
def dist(a,z): #a=awal z=akhir
d_lat= (a.lat-z.lat)**2
d_long= (a.lon-z.lon)**2
jarak = (d_lat+d_long)**0.5
return jarak
def gerak_drone():
for i in range(0,len(lat)):
print(i)
wp = LocationGlobalRelative(lat[i],lon[i],2)
vehicle.simple_goto(wp)
sleep(1)
while (dist(vehicle.location.global_relative_frame,wp)>=0.0001):
print (str(round(dist(vehicle.location.global_relative_frame,wp)*100000,2)))
while True:
_,frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
mask = cv2.inRange(hsv, hsv_b, hsv_a)
cv2.imshow("warna", mask)
cv2.imshow("hitamPutih", gray)
cv2.imshow("apa", frame)
print(cv2.countNonZero(mask))
if cv2.waitKey(500) == 27 or cv2.countNonZero(mask) > treshold :
break
if __name__ == "_main_":
altitude = 2
lat_distance = 1
lon_distance = 1
p1 = multiprocessing.Process(target=arm_and_takeoff, args=(altitude))
p2 = multiprocessing.Process(target=dist, args=(lat_distance, lon_distance))
p3 = multiprocessing.Process(target=gerak_drone)
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
print("Coming back")
vehicle.mode = VehicleMode("RTL")
sleep(20)
vehicle.mode = VehicleMode("LAND")
Here is my terminal result

PyQt5 - How to calculate corner points of a QGraphicsRectItem after rotation by its center point?

My problem is that I couldn't find the pixel values of each corner points of a HighwayItem (which is a QGraphicsRectItem) after rotation it by angle theta about the center point of it.
I used the Rotation Matrix which explained here and I also looked thisexplanation. But, I cannot find the true values.
Any help will be great. Thanks.
Here is MapViewer() class. A HighwayItem is created in this view.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, QPoint, QPointF, QRectF
from PyQt5.QtWidgets import QGraphicsScene, \
QGraphicsView, QGraphicsPixmapItem, \
from class_graphical_items import HighwayItem
class MapViewer(QGraphicsView):
def __init__(self, parent, ui):
super(MapViewer, self).__init__(parent)
self.ui = ui
# Attributes for highway
self.add_highway_control = False
self.current_highway = None
self.start = QPointF()
self.hw_counter = 0
self._scene = QGraphicsScene(self)
self._map = QGraphicsPixmapItem()
self._scene.addItem(self._map)
self.setScene(self._scene)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setFrameShape(QtWidgets.QFrame.NoFrame)
def mousePressEvent(self, event):
if self._map.isUnderMouse():
if self.add_highway_control:
# Create a yellow highway
self.current_highway = HighwayItem(self._scene, self.ui)
self.hw_counter += 1
self.start = self.mapToScene(event.pos()).toPoint()
r = QRectF(self.start, self.start)
self.current_highway.setRect(r)
self._scene.addItem(self.current_highway)
# When adding HW, set drag mode NoDrag
self.setDragMode(QGraphicsView.NoDrag)
super(MapViewer, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.add_highway_control and self.current_highway is not None:
# When adding HW, set drag mode NoDrag
self.setDragMode(QGraphicsView.NoDrag)
r = QRectF(self.start, self.mapToScene(event.pos()).toPoint()).normalized()
self.current_highway.setRect(r)
super(MapViewer, self).mouseReleaseEvent(event)
def mouseReleaseEvent(self, event):
if self.add_highway_control:
if self.current_highway is not None:
# When finish the adding HW, set drag mode ScrollHandDrag
self.setDragMode(QGraphicsView.ScrollHandDrag)
self.update_item_dict(self.current_highway)
self.update_item_table(self.current_highway)
self.current_highway = None
self.add_highway_control = False
super(MapViewer, self).mouseReleaseEvent(event)
This is the HighwayItem class. It has some specs like color, opacity etc.
By doubleclicking on created HighwayItem, I'm activating a spinbox which was in a QTreeWidget in main window (ui).
By changing the spinbox value, the user can rotate the item.
class HighwayItem(QGraphicsRectItem):
def __init__(self, scene, ui):
QGraphicsRectItem.__init__(self)
self.scene = scene
self.ui = ui
self.setBrush(QtCore.Qt.yellow)
self.setOpacity(0.5)
self.setZValue(4.0)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.setAcceptHoverEvents(True)
# Here, I'm activating the spinbox by double clicking
# on HighwayItem. In spinbox, I'm entering the rotation angle
# of HighwayItem.
def mouseDoubleClickEvent(self, event):
selected_item = self.scene.selectedItems()
if selected_item:
for i in range(self.ui.treeWidget_objects.topLevelItemCount()):
toplevel_item = self.ui.treeWidget_objects.topLevelItem(i)
heading_item = toplevel_item.child(2)
spinbox = self.ui.treeWidget_objects.itemWidget(heading_item, 2)
if str(toplevel_item.text(2)) == str(selected_item[0]):
if 'HighwayItem' in str(selected_item[0]):
spinbox.setEnabled(True)
else:
spinbox.setEnabled(False)
This is the HWHeadingSpinBox() class which sets the rotation angle of HWItem. My problem starts here. In rotate_hw() method, I am transforming the created HighwayItem by its center point and giving it a rotation by its center point.
BUT, when I try to calculate new corners of hw in calc_rotated_coords() method, I'm messing up.
class HWHeadingSpinBox(QSpinBox):
def __init__(self, viewer, selected_hw):
QSpinBox.__init__(self)
self.selected_hw = selected_hw
self.viewer = viewer
# First coords of HW
tl = self.selected_hw.rect().topLeft()
tr = self.selected_hw.rect().topRight()
br = self.selected_hw.rect().bottomRight()
bl = self.selected_hw.rect().bottomLeft()
self.temp_list = [tl, tr, br, bl]
self.setRange(-180, 180)
self.setSuffix('°')
self.setEnabled(False)
self.valueChanged.connect(self.rotate_hw)
def heading_val(self):
return self.value()
def rotate_hw(self):
angle = self.heading_val()
self.selected_hw.prepareGeometryChange()
offset = self.selected_hw.boundingRect().center()
self.selected_hw.sceneBoundingRect().center()
transform = QTransform()
transform.translate(offset.x(), offset.y())
transform.rotate(-angle)
transform.translate(-offset.x(), -offset.y())
self.selected_hw.setTransform(transform)
# br_rect = self.selected_hw.sceneBoundingRect()
# sbr_rect = self.selected_hw.sceneBoundingRect()
# r_rect = self.selected_hw.sceneBoundingRect()
#
# rectitem = QtWidgets.QGraphicsRectItem(br_rect)
# rectitem.setBrush(Qt.red)
# self.viewer._scene.addItem(rectitem)
#
# rectitem = QtWidgets.QGraphicsRectItem(sbr_rect)
# rectitem.setBrush(Qt.green)
# self.viewer._scene.addItem(rectitem)
#
# rectitem = QtWidgets.QGraphicsRectItem(r_rect)
# rectitem.setBrush(Qt.blue)
# self.viewer._scene.addItem(rectitem)
def calc_rotated_coords(self):
# center point
cx = self.selected_hw.rect().center().x()
cy = self.selected_hw.rect().center().y()
# rotation angle
theta = math.radians(angle)
rotated_corners = []
for item in self.temp_list:
x = item.x()
y = item.y()
temp_x = x - cx
temp_y = y - cy
rot_x = temp_x * math.cos(theta) + temp_y * math.sin(theta)
rot_y = -temp_x * math.sin(theta) + temp_y * math.cos(theta)
rotated_corners.append([rot_x, rot_y])
self.temp_list = rotated_corners
print("\nPIXEL VALUES OF HW: \n{}".format(self.temp_list))
Here is the solution:
I added the itemChange(self, change, value) event in to HighwayItem and if change is ItemPositionHasChanged, I calculated all items' corners as such:
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionHasChanged:
top_left = self.mapToScene(self.rect().topLeft())
top_right = self.mapToScene(self.rect().topRight())
bottom_left = self.mapToScene(self.rect().bottomLeft())
bottom_right = self.mapToScene(self.rect().bottomRight())
changed_pos = [top_left, top_right, bottom_right, bottom_left]
return super(HighwayItem, self).itemChange(change, value)

Is there a way to have multiple rectangles displayed at the same time using visual.Rect in PsychoPy?

I'm trying to create stimuli that consist of 100 small lines in the centre of the screen, with orientations sampled from a Gaussian distribution (please see the image link below):
Orientation stimuli
I've managed to achieve something that almost fits the bill, but this code only works in isolation:
from psychopy import visual, core, event
import numpy as np
from numpy.random import random
import random
Lines = visual.Rect(
win=win, name='Lines',
width=(0.015, 0.0025)[0], height=(0.015, 0.0025)[1],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1, depth=-2.0, interpolate=True)
lines_hpos = np.random.uniform(-0.49,0.49,100)
mu = 315
sigma = 15
for i in range(100):
Lines.pos = [lines_hpos[i],np.random.uniform(-0.49,0.49)]
Lines.ori = random.gauss(mu, sigma)
I've tried to manipulate this code so that I can integrate it into the full experiment I'm designing in PsychoPy's experiment builder. I run the below code in the experiment builder's coding window calling 'gdist' and 'loc' as values for the 'Orientation' and 'Position' of the rectangles, respectively:
import random
gdist =[]
loc = []
lines_hpos = np.random.uniform(-0.49,0.49,100)
mu = 90
sigma = 20
for i in range(100):
rloc = [lines_hpos[i],np.random.uniform(-0.49,0.49)]
loc.append(rloc)
gauss = random.gauss(mu, sigma)
gdist.append(gauss)
When I attempt to run the experiment, I get an error return and the experiment fails to start:
File "C:\Users\r02mj20\AppData\Local\PsychoPy3\lib\site-packages\psychopy\visual\image.py", line 238, in __del__
File "C:\Users\r02mj20\AppData\Local\PsychoPy3\lib\site-packages\pyglet\gl\lib.py", line 97, in errcheck
ImportError: sys.meta_path is None, Python is likely shutting down
I'm assuming this has something to do with pyglet not liking the idea of there being 100 rectangles all at once (side note: the script works fine if range(1)). If anyone has any suggestions for how I might fix or work around this problem, I'd be eternally grateful.
i don't see any problem with this idea, except you better use visual.Line instead of Rect, and your units of measure are not described; the key to preserving video memory is BufferImageStim, btw
from psychopy import visual, core, event, monitors
from psychopy.iohub.client import launchHubServer
import random
import numpy as np
MU = 315; SIGMA = 15
num_lines = 100
io = launchHubServer(iohub_config_name='iohub_config.yaml')
display = io.devices.display
mon = monitors.Monitor(name = display.getPsychopyMonitorName())
win = visual.Window([640, 480], units='pix', viewScale = 1.0,
monitor = mon, winType='pyglet',
fullScr = False, waitBlanking = True, useFBO = True, useLights = False,
allowStencil=False, allowGui = True,
screen = display.getIndex(), colorSpace = 'rgb255', color = [128,128,128],
name = 'my_win01')
rects = []
lines_hpos = np.random.uniform(-0.49, 0.49, num_lines)
for i in range(num_lines):
line_rect = visual.Rect(win=win, size=(0.001, 1.0), units='norm',
pos=(0,0), lineWidth=1, lineColor=[1,1,1], fillColor=[1,1,1], opacity=1, depth=-2.0,
name='lines_rect', interpolate=True, autoLog=False, autoDraw=False)
line_rect.pos = [lines_hpos[i], np.random.uniform(-0.49,0.49)]
line_rect.ori = random.gauss(MU, SIGMA)
rects.append(line_rect)
rect_buffer = visual.BufferImageStim(win, buffer='back', stim=rects, sqPower2=False, interpolate=False, name='rect-buffer', autoLog=True)
rect_buffer.draw()
win.flip()
event.waitKeys()

Own drag icon with same color and font settings as the default drag icon in a Gtk.TreeView

The Gtk.TreeView implements a default drag icon. It use the background color of the TreeView, it's font and the complete row-content as string.
I want the same (background-color, font-face, font-size, font-color) but with a shorter string (only the second of three columns).
In the example below create my own cairo.Surface to create such an icon. But color and font is a problem. I don't know how to set them up or (much more important) to ask the TreeView or Gtk itself for the current color and font values.
How does the TreeView get this values?
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
import cairo
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="TreeView Drag and Drop")
self.connect("delete-event", Gtk.main_quit)
self.box = Gtk.Box()
self.add(self.box)
# "model" with dummy data
self.store = Gtk.TreeStore(int, str, int)
for i in range(5):
self.store.append(None, [i, 'Item {}'.format(i), i]) # treeview
self.tree = Gtk.TreeView(model=self.store)
self.box.pack_start(self.tree, True, True, 0)
# build columns
colA = Gtk.TreeViewColumn('Col A', Gtk.CellRendererText(), text=0)
self.tree.append_column(colA)
colB = Gtk.TreeViewColumn('Col B', Gtk.CellRendererText(), text=1)
self.tree.append_column(colB)
colC = Gtk.TreeViewColumn('Col C', Gtk.CellRendererText(), text=2)
self.tree.append_column(colC)
# enable default drag and drop
self.tree.set_reorderable(True)
# DnD events
self.tree.connect_after("drag-begin", self.drag_begin)
def drag_begin(self, widget, context):
model, path = widget.get_selection().get_selected_rows()
text = model[path][1]
# dummy surface/context
surface = cairo.ImageSurface(cairo.Format.RGB24, 0, 0)
cr = cairo.Context(surface)
# calculate text size
txtext = cr.text_extents(text)
width = int(txtext.width)
height = int(txtext.height)
offset = 10
# creal surface/context
surface = cairo.ImageSurface(cairo.Format.RGB24,
width + (offset*2),
height + (offset*2))
cr = cairo.Context(surface)
cr.set_source_rgb(1, 1, 1) # text color: white
cr.move_to(0+offset, height+offset)
cr.show_text(text)
# use the surface as drag icon
Gtk.drag_set_icon_surface(context, surface)
win = MainWindow()
win.show_all()
Gtk.main()
What I tried (but not worked) was cairo.Surface.create_similar()',cairo.Surface.create_similar_image()andGtk.TreeView.create_row_drag_icon()`.
This answer is based on a foreign mailing list posting.
The widget has a Gtk.StyleContext. A Pango.Layout is used to render the text based on the style informations in the Gtk.StyleContext.
def drag_begin(self, widget, context):
model, path = widget.get_selection().get_selected_rows()
text = model[path][1]
stylecontext = widget.get_style_context()
# new pango layout
pl = widget.create_pango_layout(text)
ink_rec, log_rect = pl.get_pixel_extents()
padding = 5
# create surface/context
surface = cairo.ImageSurface(cairo.Format.RGB24,
log_rect.width + (padding*2),
log_rect.height + (padding*2))
cr = cairo.Context(surface)
Gtk.render_background(stylecontext, cr, 0, 0,
log_rect.width + (padding*2),
log_rect.height + (padding*2))
Gtk.render_layout(stylecontext, cr, padding, padding, pl)
# border
line_width = cr.get_line_width()
cr.rectangle(-1+line_width, -1+line_width,
log_rect.width+(padding*2)-line_width,
log_rect.height+(padding*2)-line_width)
cr.stroke()
# use the surface as drag icon
Gtk.drag_set_icon_surface(context, surface)