Draw vertical lines on QTextEdit in PyQt - python

I am trying to develop a GUI that contains a QTextEdit widget. When the gui loads, it pulls in data from a file where the data is in columns of fixed widths.
I want the user to be able to click at various points in the QTextEdit widget, to mark the positions where new columns start, and I want vertical lines to be drawn on the widget at those positions, to show the columns.
In my GUI init() method I had the following line to intercept the paintEvent from the text widget:
self.mytextviewer.paintEvent = self.handlePaintEvent
and I had a handlePaintEvent() method:
def handlePaintEvent(self, event):
painter = QPainter(self.mytextviewer)
pen = QPen(Qt.SolidLine)
pen.setColor(Qt.black)
pen.setWidth(1)
painter.setPen(pen)
painter.drawLine(20, 0, 20, 100)
However when I tried to run the code I started to get QPainter errors about the painter not being active.
I then tried a different direction, subclassing QTextEdit and adding basically the same code as above to the paintEvent() method of my subclass. However I am still getting the errors.
I then tried adding painter.begin(self) and painter.end()to the paintEvent() method, but had no joy with that either.
Also, the text that was initially being displayed in the widget is no longer displayed since I added my custom paintEvent() method.
Am I doing something really stupid here, or is there a better/easier way to go about this?
Thanks.

I found an answer, hopefully it might help someone else....
You need to supply QPainter with the widgets viewport when creating an instance of QPainter in the paintEvent().
To get it to display the text, include the super() method of the parent class.
def paintEvent(self, event):
painter = QPainter(self.viewport())
pen = QPen(Qt.SolidLine)
pen.setColor(Qt.black)
pen.setWidth(1)
painter.setPen(pen)
painter.drawLine(20, 0, 20, 100)
super(TextWidgetWithLines, self).paintEvent(event)

Related

drawRect() in QTextEdit not functioning properly when cursor is moved

I've got this simple code in the paintEvent of my QTextEdit which draws a grey box under the currently selected QTextBlock:
def paintEvent(self, ev):
painter = QPainter()
painter.begin(self.viewport())
currentPos = self.textCursor().position()
block = self.document().findBlock(currentPos)
rect = self.document().documentLayout().blockBoundingRect(block)
margin = self.document().documentMargin()
rect.setTopLeft(QPoint(int(rect.topLeft().x()-margin), int(rect.topLeft().y()-margin)))
rect.setBottomRight(QPoint(int(rect.bottomRight().x()+margin), int(rect.bottomRight().y())))
painter.fillRect(rect, QBrush(QColor(10, 10, 10,20)))
if self._last_selected_block and (self._last_selected_block != block):
lastrect = self.document().documentLayout().blockBoundingRect(self._last_selected_block) #clean up artifacts
painter.eraseRect(lastrect)
painter.fillRect(self.contentsRect(), QBrush(QColor(123, 111, 145, 80))) #background color
painter.end()
self._last_selected_block = block
super().paintEvent(ev)
(Note, the "clean up artifacts" line erases anything drawn in the region of the previously selected QTextBlock, since a thin grey line would remain under the last block if text was drawn in it. Might be related.)
The effect of this is:
However, when the cursor is moved via clicking on another line, this happens:
The next rectangle is only partially drawn, around the character which the cursor was moved to, and the previous one is not erased. eraseRect() doesn't seem to be able to remove this artifact. When typing is continued or a newline is made, everything goes back to normal (this issue never occurs when changing lines via making a newline). I've confirmed that paintEvent() is called when the cursor moves, and the width of the rectangles to be drawn never changes. What's happening here?
For optimization reasons, Qt only try to repaint only the portions of the widget that actually need updates.
In the case of a QTextEdit, this means that only the portions in which the "caret" was before moving it (by editing, using arrow keys or mouse) and it is now will be updates, while ignoring everything else.
This clearly is an issue in your case, because it will only update small portions of the widget, with the result that the previously highlighted block will not be redrawn showing your custom background.
The solution is to track the current cursor position and correctly update both the previous block and the new one as soon as the block changes. This is achieved by calling update() using a QRegion, created by merging the current block bounding rect and the previous (if any); this will schedule an update that only redraws the contents within that region (which is what QTextEdit normally does, but we're extending it to the whole block area and the previous).
Note that I've completely changed your implementation of the paintEvent, as it was mostly unnecessary, for the following reasons:
the margins of the document should not be used for the block;
the background painting doesn't consider the scroll area background (more about this later);
there is no need to "erase" the previous block rectangle: our update() call includes that area, and since it's not the current block, the background will be just (re)drawn there instead;
the current block shouldn't be updated in the paint event;
The rendering of a scroll area always involves painting of its background based on the backgroundRole, which is automatically set to the Base palette role for QTextEdit. The result is that your background color will not be what you believe, but a composition of the base (usually, a nearly white color) and your background.
In order to ensure that the background is exactly the color you want, you have to update the palette on the widget using that color for that Base role, which should also be an opaque color (otherwise it will be composed using the Window color role).
class TextEdit(QtWidgets.QTextEdit):
_last_selected_block = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
palette = self.palette()
palette.setColor(palette.Base, QtGui.QColor(203, 200, 210))
self.setPalette(palette)
self.cursorPositionChanged.connect(self.trackCursorPosition)
def trackCursorPosition(self):
block = self.textCursor().block()
currentRect = self.document().documentLayout().blockBoundingRect(block)
updateRegion = QtGui.QRegion(currentRect.toRect())
if self._last_selected_block and self._last_selected_block != block:
oldRect = self.document().documentLayout().blockBoundingRect(
self._last_selected_block)
updateRegion |= QtGui.QRegion(oldRect.toRect())
updateRegion.translate(0, -self.verticalScrollBar().value())
self._last_selected_block = block
self.viewport().update(updateRegion)
def paintEvent(self, ev):
painter = QtGui.QPainter(self.viewport())
block = self.textCursor().block()
rect = self.document().documentLayout().blockBoundingRect(block)
rect.translate(0, -self.verticalScrollBar().value())
painter.fillRect(rect, QtGui.QColor(10, 10, 10,20))
super().paintEvent(ev)

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!

wxPython not showing grid

I am using wxPython to make a gui. Currently I have a menubar, and three panels. I want to have a grid show up in the second panel when I click a button. However. When I click on the button, all I get is a small grey rectangle.
Here is the code for the button:
self.Bind(wx.EVT_BUTTON, self.OnCo, id=self.submit.GetId())
and here is the code for the "OnCo" event when the button is clicked:
def OnCo(self, e):
#to get rid of stuff that was previously in the panel
for child in self.panel2.GetChildren():
child.Destroy()
for child in self.panel3.GetChildren():
child.Destroy()
mygrid = gridlib.Grid(self.panel2, -1)
mygrid.CreateGrid(500,7)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(mygrid, -1, wx.EXPAND)
self.panel2.SetSizer(sizer)
mygrid.SetColLabelValue(0, 'S')
mygrid.SetColLabelValue(1, 'PB')
mygrid.SetColLabelValue(2, 'P')
mygrid.SetColLabelValue(3, 'T')
mygrid.SetColLabelValue(4, 'D')
Any help on how I can get my grid to show? Thanks.
It's possible that the grid is not sizing correctly; your items may be there, but it's not showing everything. After changing values in a grid, I always make sure to update the sizing of it. I usually just add a simple function to the class like this:
def SetGridSize(self):
self.mygrid.AutoSizeRows()
self.mygrid.AutoSizeColumns()
self.sizer.Fit(self)
and then call SetGridSize() whenever I change the values to make sure the whole thing shows on the screen instead of getting cut off.
Of course, you'll have to adapt it a bit to your names and whatnot. This implementation assumes the class is a wx.Frame object.

Custom calendar cell in PyQt

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.

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