""" A text editor that automatically adjusts its height to the height of the text in its document when managed by a layout. """ from PyQt5.QtWidgets import QTextEdit, QSizePolicy from PyQt5.QtGui import QFontMetrics from PyQt5.QtCore import QSize class AutoResizingTextEdit(QTextEdit): def __init__(self, parent = None): super(AutoResizingTextEdit, self).__init__(parent) # This seems to have no effect. I have expected that it will cause self.hasHeightForWidth() # to start returning True, but it hasn't - that's why I hardcoded it to True there anyway. # I still set it to True in size policy just in case - for consistency. size_policy = self.sizePolicy() size_policy.setHeightForWidth(True) size_policy.setVerticalPolicy(QSizePolicy.Preferred) self.setSizePolicy(size_policy) self.textChanged.connect(lambda: self.updateGeometry()) def setMinimumLines(self, num_lines): """ Sets minimum widget height to a value corresponding to specified number of lines in the default font. """ self.setMinimumSize(self.minimumSize().width(), self.lineCountToWidgetHeight(num_lines)) def hasHeightForWidth(self): return True def heightForWidth(self, width): margins = self.contentsMargins() if width >= margins.left() + margins.right(): document_width = width - margins.left() - margins.right() else: # If specified width can't even fit the margin, there's no space left for the document document_width = 0 # Cloning the whole document only to check its size at different width seems wasteful # but apparently it's the only and preferred way to do this in Qt >= 4. QTextDocument does not # provide any means to get height for specified width (as some QWidget subclasses do). # Neither does QTextEdit. In Qt3 Q3TextEdit had working implementation of heightForWidth() # but it was allegedly just a hack and was removed. # # The performance probably won't be a problem here because the application is meant to # work with a lot of small notes rather than few big ones. And there's usually only one # editor that needs to be dynamically resized - the one having focus. document = self.document().clone() document.setTextWidth(document_width) return margins.top() + document.size().height() + margins.bottom() def sizeHint(self): original_hint = super(AutoResizingTextEdit, self).sizeHint() return QSize(original_hint.width(), self.heightForWidth(original_hint.width())) def lineCountToWidgetHeight(self, num_lines): """ Returns the number of pixels corresponding to the height of specified number of lines in the default font. """ # ASSUMPTION: The document uses only the default font assert num_lines >= 0 widget_margins = self.contentsMargins() document_margin = self.document().documentMargin() font_metrics = QFontMetrics(self.document().defaultFont()) # font_metrics.lineSpacing() is ignored because it seems to be already included in font_metrics.height() return ( widget_margins.top() + document_margin + max(num_lines, 1) * font_metrics.height() + self.document().documentMargin() + widget_margins.bottom() ) return QSize(original_hint.width(), minimum_height_hint)