I am working on a PyQt application and one of the functions is to change the cursor style when user opens the app. It is easy to make this work, the only problem is the hotspot information is default to half of the image's width and height and there is not a certainty that all cursor image have their hotspot info just locate on the center of the image. So I want to get these info from a cur file and set these info by calling QWidget
's setCursor
method. I have no idea how to get those position information using PyQt.
My code is like this:
@staticmethod
def setCursor(widget: QWidget, cursorIconPath: str):
widget.setCursor(QCursor(QPixmap(cursorIconPath)))
Please note that the cursor resource file is a .cur
file, and technically there is a hotspot info in this file. I also found that QCursor
have a method hotSpot()
to get this info as a QPoint
.
I know there may be some way to get it out of PyQt, like a image editor, but it is troublesome because I need to set this hotspot info in my PyQt application every time I want to change the cursor file.
Is there any way to solve my problem? Any help would be appreciated!
When creating the cursor via QPixmap
, any hotspot information in the file will be lost, since Qt will treat it as an ICO image (which has an almost identical format). The QCursor.hotSpot()
method can only ever return the values you supply in the constructor - or a generic default calculated as roughly width [or height] / 2 / device-pixel-ratio
. So your only option here is to extract the values directly from the file and then supply them in the QCursor
constructor. Fortunately, this is quite easy to do as the structure of the file is quite easy to parse.
Below is a basic demo which shows how to achieve this. (Click on the items to test the cursors).
from PyQt5 import QtCore, QtGui, QtWidgets
def create_cursor(path):
curfile = QtCore.QFile(path)
if curfile.open(QtCore.QIODevice.ReadOnly):
pixmap = QtGui.QPixmap.fromImage(
QtGui.QImage.fromData(curfile.readAll(), b'ICO'))
if not pixmap.isNull():
curfile.seek(10)
stream = QtCore.QDataStream(curfile)
stream.setByteOrder(QtCore.QDataStream.LittleEndian)
hx = stream.readUInt16()
hy = stream.readUInt16()
return QtGui.QCursor(pixmap, hx, hy)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Choose Cursors')
self.button.clicked.connect(self.handleButton)
self.view = QtWidgets.QListWidget()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.button)
self.view.itemClicked.connect(self.handleItemClicked)
def handleButton(self):
files = QtWidgets.QFileDialog.getOpenFileNames(
self, 'Choose Cursors', QtCore.QDir.homePath(),
'Cursor Files (*.cur)')[0]
if files:
self.view.clear()
for filepath in files:
cursor = create_cursor(filepath)
if cursor is not None:
item = QtWidgets.QListWidgetItem(self.view)
item.setIcon(QtGui.QIcon(cursor.pixmap()))
item.setText(QtCore.QFileInfo(filepath).baseName())
item.setData(QtCore.Qt.UserRole, cursor)
def handleItemClicked(self, item):
self.setCursor(item.data(QtCore.Qt.UserRole))
if __name__ == '__main__':
app = QtWidgets.QApplication(['Test'])
window = Window()
window.setGeometry(600, 100, 250, 350)
window.show()
app.exec()