Custom calendar cell in PyQt - python

I would like to make a custom cell for PyQt.QtGui.QCalendarWidget. I need to add a short small text to the bottom of some calendar cells, is it possible? If so, could anyone point me in the right direction?
So far the idea is to subclass the QCalendarWidget and override its paintCell method. However I have no idea what to do there. I've tried to look at the Qt and the code of the widget, but it's pretty complex and I didn't find the place where they actualy paint the cell. So I would appreciate any advice.

You don't need to reimplement painting. Just call the default implementation and add text drawing:
class MyCalendar(QtGui.QCalendarWidget):
def __init__(self,parent=None):
QtGui.QCalendarWidget.__init__(self,parent)
def paintCell(self, painter, rect, date):
QtGui.QCalendarWidget.paintCell(self, painter, rect, date)
if date.day() % 5 == 0: # example condition based on date
painter.drawText(rect.bottomLeft(), "test")
You need to set proper height for the widget to ensure that there is enough space in cells to display the text.

Related

How to draw rectangle on Qlabel of image with mouse in the PyQt5 [duplicate]

I display images with Qlabel.I need image coordinates/pixel coordinates but, I use mouseclickevent its show me only Qlabel coordinates.
for examples my image is 800*753 and my Qlabel geometry is (701,451).I reads coordinates in (701,451) but I need image coordinates in (800*753)
def resimac(self):
filename= QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
self.image=QtGui.QImage(filename[0])
self.pixmap=QtGui.QPixmap.fromImage(self.image)
self.resim1.setPixmap(self.pixmap)
self.resim1.mousePressEvent=self.getPixel
def getPixel(self, event):
x = event.pos().x()
y = event.pos().y()
print("X=",x," y= ",y)
Since you didn't provide a minimal, reproducible example, I'm going to assume that you're probably setting the scaledContents property, but that could also be not true (in case you set a maximum or fixed size for the label).
There are some other serious issues about your answer, I'll address them at the end of this answer.
The point has to be mapped to the pixmap coordinates
When setting a pixmap to a QLabel, Qt automatically resizes the label to its contents.
Well, it does it unless the label has some size constrains: a maximum/fixed size that is smaller than the pixmap, and/or the QLabel has the scaledContents property set to True as written above. Note that this also happens if any of its ancestors has some size constraints (for example, the main window has a maximum size, or it's maximized to a screen smaller than the space the window needs).
In any of those cases, the mousePressEvent will obviously give you the coordinates based on the widget, not on the pixmap.
First of all, even if it doesn't seem to be that important, you'll have to consider that every widget can have some contents margins: the widget will still receive events that happen inside the area of those margins, even if they are outside its actual contents, so you'll have to consider that aspect, and ensure that the event happens within the real geometry of the widget contents (in this case, the pixmap). If that's true, you'll have to translate the event position to that rectangle to get its position according to the pixmap.
Then, if the scaledContents property is true, the image will be scaled to the current available size of the label (which also means that its aspect ratio will not be maintained), so you'll need to scale the position.
This is just a matter of math: compute the proportion between the image size and the (contents of the) label, then multiply the value using that proportion.
# click on the horizontal center of the widget
mouseX = 100
pixmapWidth = 400
widgetWidth = 200
xRatio = pixmapWidth / widgetWidth
# xRatio = 2.0
pixmapX = mouseX * xRatio
# the resulting "x" is the horizontal center of the pixmap
# pixmapX = 200
On the other hand, if the contents are not scaled you'll have to consider the QLabel alignment property; it is usually aligned on the left and vertically centered, but that depends on the OS, the style currently in use and the localization (consider right-to-left writing languages). This means that if the image is smaller than the available size, there will be some empty space within its margins, and you'll have to be aware of that.
In the following example I'm trying to take care about all of that (I'd have to be honest, I'm not 100% sure, as there might be some 1-pixel tolerance due to various reasons, most regarding integer-based coordinates and DPI awareness).
Note that instead of overwriting mousePressEvent as you did, I'm using an event filter, I'll explain the reason for it afterwards.
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout(self)
self.getImageButton = QtWidgets.QPushButton('Select')
layout.addWidget(self.getImageButton)
self.getImageButton.clicked.connect(self.resimac)
self.resim1 = QtWidgets.QLabel()
layout.addWidget(self.resim1)
self.resim1.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
# I'm assuming the following...
self.resim1.setScaledContents(True)
self.resim1.setFixedSize(701,451)
# install an event filter to "capture" mouse events (amongst others)
self.resim1.installEventFilter(self)
def resimac(self):
filename, filter = QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
if not filename:
return
self.resim1.setPixmap(QtGui.QPixmap(filename))
def eventFilter(self, source, event):
# if the source is our QLabel, it has a valid pixmap, and the event is
# a left click, proceed in trying to get the event position
if (source == self.resim1 and source.pixmap() and not source.pixmap().isNull() and
event.type() == QtCore.QEvent.MouseButtonPress and
event.button() == QtCore.Qt.LeftButton):
self.getClickedPosition(event.pos())
return super().eventFilter(source, event)
def getClickedPosition(self, pos):
# consider the widget contents margins
contentsRect = QtCore.QRectF(self.resim1.contentsRect())
if pos not in contentsRect:
# outside widget margins, ignore!
return
# adjust the position to the contents margins
pos -= contentsRect.topLeft()
pixmapRect = self.resim1.pixmap().rect()
if self.resim1.hasScaledContents():
x = pos.x() * pixmapRect.width() / contentsRect.width()
y = pos.y() * pixmapRect.height() / contentsRect.height()
pos = QtCore.QPoint(x, y)
else:
align = self.resim1.alignment()
# for historical reasons, QRect (which is based on integer values),
# returns right() as (left+width-1) and bottom as (top+height-1),
# and so their opposite functions set/moveRight and set/moveBottom
# take that into consideration; using a QRectF can prevent that; see:
# https://doc.qt.io/qt-5/qrect.html#right
# https://doc.qt.io/qt-5/qrect.html#bottom
pixmapRect = QtCore.QRectF(pixmapRect)
# the pixmap is not left aligned, align it correctly
if align & QtCore.Qt.AlignRight:
pixmapRect.moveRight(contentsRect.x() + contentsRect.width())
elif align & QtCore.Qt.AlignHCenter:
pixmapRect.moveLeft(contentsRect.center().x() - pixmapRect.width() / 2)
# the pixmap is not top aligned (note that the default for QLabel is
# Qt.AlignVCenter, the vertical center)
if align & QtCore.Qt.AlignBottom:
pixmapRect.moveBottom(contentsRect.y() + contentsRect.height())
elif align & QtCore.Qt.AlignVCenter:
pixmapRect.moveTop(contentsRect.center().y() - pixmapRect.height() / 2)
if not pos in pixmapRect:
# outside image margins, ignore!
return
# translate coordinates to the image position and convert it back to
# a QPoint, which is integer based
pos = (pos - pixmapRect.topLeft()).toPoint()
print('X={}, Y={}'.format(pos.x(), pos.y()))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
Now. A couple of suggestions.
Don't overwrite existing child object methods with [other] object's instance attributes
There are various reasons for which this is not a good idea, and, while dealing with Qt, the most important of them is that Qt uses function caching for virtual functions; this means that as soon as a virtual is called the first time, that function will always be called in the future. While your approach could work in simple cases (especially if the overwriting happens within the parent's __init__), it's usually prone to unexpected behavior that's difficult to debug if you're not very careful.
And that's exactly your case: I suppose that resimac is not called upon parent instantiation and until after some other event (possibly a clicked button) happens. But if the user, for some reason, clicks on the label before a new pixmap is loaded, your supposedly overwritten method will never get called: at that time, you've not overwritten it yet, so the user clicks the label, Qt calls the QLabel's base class mousePressEvent implementation, and then that method will always be called from that point on, no matter if you try to overwrite it.
To work around that, you have at least 3 options:
use an event filter (as the example above); an event filter is something that "captures" events of a widgets and allows you to observe (and interact) with it; you can also decide to propagate that event to the widget's parent or not (that's mostly the case of key/mouse events: if a widget isn't "interested" about one of those events, it "tells" its parent to care about it); this is the simplest method, but it can become hard to implement and debug for complex cases;
subclass the widget and manually add it to your GUI within your code;
subclass it and "promote" the widget if you're using Qt's Designer;
You don't need to use a QImage for a QLabel.
This is not that an issue, it's just a suggestion: QPixmap already uses (sort of) fromImage within its C++ code when constructing it with a path as an argument, so there's no need for that.
Always, always provide usable, Minimal Reproducible Example code.
See:
https://stackoverflow.com/help/how-to-ask
https://stackoverflow.com/help/minimal-reproducible-example
It could take time, even hours to get an "MRE", but it's worth it: there'll always somebody that could answer you, but doesn't want to or couldn't dig into your code for various reasons (mostly because it's incomplete, vague, inusable, lacking context, or even too expanded). If, for any reason, there'll be just that one user, you'll be losing your occasion to solve your problem. Be patient, carefully prepare your questions, and you'll probably get plenty of interactions and useful insight from it.

Auto resize QLabel to fit updated QTableWidget span

I have a QTableWidget, and I have a QLabel that I put inside a cell of the table using .setCellWidget().
During run-time I change the span of the QTableWidget cell where the QLabel is, using .setSpan()
BUT when I change the span of the cell where the QLabel is, the QLabel does not resize.
Some code and screenshot below:
def generate_table(self):
global gtable
table = QTableWidget(20, 60)
gtable = table
def create_task(self):
task_widget = QWidget()
task_layout = QHBoxLayout()
task_widget.setLayout(task_layout)
task = QLabelClickable(the_task_name)
task_layout.addWidget(task)
gtable.setCellWidget(selected_row_column[0], selected_row_column[1], task_widget)
// if I include this part of the code, everything looks fine, both cell, widget and label scale properly, as visible through background color, below line is not the problem, notice its in the same function as where I set the cell widget
the_duration = 3
gtable.setSpan(selected_row, selected_column, 1, the_duration)
// Below is how I change the cell span. The rrow and ccolumn are integers, basically just cell coordinates
def save_task(self):
gtable.setSpan(rrow, ccolumn, 1, w_dr.value())
(there's a ton of code all over the place so I included what I thought is relevant, let me know what other parts of the code I should include)
This is what it should look like: (this is what the first span-changing line does)
This is what it looks like: (this is what the last line does)
My question is, How do I resize the QLabel / QWidget to auto fit the cell's updated size?
You can see the first span as correct because it's applied in the same event loop that would update the geometries of the view.
Spanning does not automatically do that (which could be a bug), so the solution is to use updateGeometries(), which:
Updates the geometry of the child widgets of the view.
Which means that all widgets of the view will be correctly resized and updated, including scroll bars and cell widgets.
def save_task(self):
gtable.setSpan(rrow, ccolumn, 1, w_dr.value())
gtable.updateGeometries()
A very important suggestion: avoid using globals, they are not handy as one would think, and in fact the often cause problems and bugs that are difficult to track; use instance members instead (eg. self.gtable).

Changing icon in nested menus

I don't think this may be posssible but I had still want to try asking.
In the attached screenshot, I have nested menus.
Is it possible to change the arrow keys icon as 'highlighted' by the red box?
I am trying to change the arrow key to a plus icon if there are no sub menu items found.
The default arrow can be in use if there are sub menu items found.
Yes, you can change the color of right-arrow.
But there is a trick to change it.
The truth of indicator is "branch-closed png file"
You can see the png file at the almost bottom on the page in the link.
So, it can not be solved by the pure-programmic way.
You prepare the picture in advance by yourself.
and please following code in QMenu constructor.
self.setStyleSheet("QMenu::right-arrow{image:url(stylesheet-branch-closed-red.png);}")
Attention:
stylesheet-branch-closed-red.png is my renamed picture.
You can download the original picture from the above link page.
you right-click the png picture and save as name.
This code comes from your past question.
class QCustomMenu(QtGui.QMenu):
"""Customized QMenu."""
def __init__(self, title, parent=None):
super(QCustomMenu, self).__init__(title=str(title), parent=parent)
self.setup_menu()
self.setStyleSheet("QMenu::right-arrow{image:url(stylesheet-branch-closed-red.png);}")
def setup_menu(self):
self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
def contextMenuEvent(self, event):
no_right_click = [QAddAction]
if any([isinstance(self.actionAt(event.pos()), instance) for instance in no_right_click]):
return
pos = event.pos()
def addAction(self, action):
super(QCustomMenu, self).addAction(action)
As the result, it will become like this.
You will dislike the white part of the arrow.
No problem, you can delete them clearly with a free-paint soft, but I didn't do it because it was needless.

How to get the position of a widget in pyqt5, while using a layout method

How do i get a PyQt5 widgets coordinates relative to it's parent widget? Specifically when using layout method, in my case i am using the QGridLayout layout method.
I have tried to get the widgets coordinates using the .x() and .y() methods. But both of these methods both return 0, I believe this is because i am using a layout method and that point is referring to the starting corner of the widget, which does not help in my case.
I have also tried using the mapToGlobal() and mapToParent() methods, I Do not believe I am using them correctly though,here are some examples of ways I have tried to use these two methods.
map_it = self.Play_label.mapFromParent(QPoint(1,1))
print ("Label coord: ", map_it)
map_it = self.Play_label.mapToParent(QPoint(1,1))
print ("Label coord: ", map_it)
I have also tried adding this method to the widget itself, in this case i have a class widget with the name Play_label, and i make a method in this class that prints out the mapToParent method. Below i will show an example.
def map_to_parent(self):
print ("From Widget Coord: ", self.mapFromParent(QPoint(0,0)))
But these all return whatever i put as the QPoint, for example the first two examples will print out PyQt5.QtCore.QPoint(1,1). While the last example will print out PyQt5.QtCore.QPoint(). Why is this am I not using these methods correctly?
Basically what I am trying to do is use the QPropertyAnimation to change the position of the QWidget that i created, which is a QLabel that i set the Pixmap of plus various other functions. Normally you would change the geometry to change the position of a widget, but this does not work in my case of course because I am using a layout method. Here is an example of that code.
self.animation.setDuration(3000)
self.animation.setStartValue(QRect(0,0,200,200))
self.animation.setEndValue(QRect(width_mul, height_mul, 150, 150))
#print ("Value: ", self.animation.currentValue())
#self.animation.valueChanged.connect(self.Play_label.changeImageSize)
#self.animation.setTargetObject(self.Play_label)
self.animation.start()
If someone could please explain to me how to get the coordinates of a widget from the parent widget when using a layout method that would be huge help, or maybe i am using the QPropertyAnimation() method wrong i am really not sure, again all i am trying to do is change the position of the widget using the QPropertyAnimation, I want it to come in from the side and move to where the widget is normally positioned, which i need the coordinates of the Widget for or some other way to move it, any advice will be greatly appreciated, thank you in advanced!

Scrolling QGraphicsView programmatically

I've want to implement a scroll/pan-feature on a QGraphicsView in my (Py)Qt application. It's supposed to work like this: The user presses the middle mouse button, and the view scrolls as the user moves the mouse (this is quite a common feature).
I tried using the scroll() method inherited from QWidget. However, this somehow moves the view instead - scrollbars and all. See picture.
So, given that this is not the way I'm supposed to do this, how should I? Or is it the correct way, but I do something else wrong? The code I use:
def __init__(self):
...
self.ui.imageArea.mousePressEvent=self.evImagePress
self.ui.imageArea.mouseMoveEvent=self.evMouseMove
self.scrollOnMove=False
self.scrollOrigin=[]
...
def evImagePress(self, event):
if event.button() == Qt.LeftButton:
self.evImageLeftClick(event)
if event.button() == Qt.MidButton:
self.scrollOnMove=not self.scrollOnMove
if self.scrollOnMove:
self.scrollOrigin=[event.x(), event.y()]
...
def evMouseMove(self, event):
if self.scrollOnMove:
self.ui.imageArea.scroll(event.x()-self.scrollOrigin[0],
event.y()-self.scrollOrigin[1])
It works as I expect, except for the whole move-the-widget business.
Fails to scroll http://img55.imageshack.us/img55/3222/scrollfail.jpg
My addition to translate() method.
It works great unless you scale the scene. If you do this, you'll notice, that the image is not in sync with your mouse movements. That's when mapToScene() comes to help. You should map your points from mouse events to scene coordinates. Then the mapped difference goes to translate(), voila viola- your scene follows your mouse with a great precision.
For example:
QPointF tmp2 = mapToScene(event->pos());
QPointF tmp = tmp2.mapToScene(previous_point);
translate(tmp.x(),tmp.y());
I haven't done this myself but this is from the QGraphicsView documentation
... When the scene is larger
than the scroll bars' values, you can
choose to use translate() to navigate
the scene instead.
By using scroll you are moving the widget, translate should achieve what you are looking for, moving the contents of the QGraphicsScene underneath the view
Answer given by denis is correct to get translate to work. The comment by PF4Public is also valid: this can screw up scaling. My workaround is different than P4FPublc's -- instead of mapToScene I preserve the anchor and restore it after a translation:
previousAnchor = view.transformationAnchor()
#have to set this for self.translate() to work.
view.setTransformationAnchor(QGraphicsView.NoAnchor)
view.translate(x_diff,y_diff)
#have to reset the anchor or scaling (zoom) stops working:
view.setTransformationAnchor(previousAnchor)
You can set the QGraphicsScene's area that will be displayed by the QGraphicsView with the method QGraphicsView::setSceneRect(). So when you press the button and move the mouse, you can change the center of the displayed part of the scene and achieve your goal.

Categories

Resources