diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a2b576..721e18e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,16 @@ -## [Unreleased] +## [v0.5.0] ### Added - +1. Add message bubbles. ### Changed 1. Use SQLAlchemy offset and limit to fetch only required messages. -### Fixed +### Build + +1. Add an entrypoint script created by setuptool. --- diff --git a/pyproject.toml b/pyproject.toml index 286b096..b8dcb38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,10 @@ fallback_version = "0.0.0" [tool.setuptools.packages.find] where = ["src"] + +[project.scripts] +memorytext = "memorytext.app:main" + [project.urls] Repository = "https://github.com/shsiddhant/memory.text" Issues = "https://github.com/shsiddhant/memory.text/issues" diff --git a/src/memorytext/delegates/message_list.py b/src/memorytext/delegates/message_list.py index 359b0ed..3fce9b9 100644 --- a/src/memorytext/delegates/message_list.py +++ b/src/memorytext/delegates/message_list.py @@ -1,80 +1,152 @@ from PySide6.QtWidgets import QStyledItemDelegate from PySide6.QtCore import Qt, QRect, QSize -from PySide6.QtGui import QFont, QFontMetrics +from PySide6.QtGui import QFont, QFontMetrics, QColor from memorytext.models.message_list import MessageList +USER_COLOR = "#ABE7FF" +OTHER_COLOR = "#e0e0e0" +TEXT_COLOR = "#000000" +USER_TEXT_COLOR = "#074D69" +TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M" +TIMESTAMP_COLOR = "#989898" +USER_TIMESTAMP_COLOR = "#2C98C2" + + class MessageDelegate(QStyledItemDelegate): def __init__(self, parent=None): super().__init__(parent) - self.padding = 8 + self.padding = 10 self.sender_height = 20 self.spacing = 5 def paint(self, painter, option, index): painter.save() + # Data text = str(index.data(Qt.ItemDataRole.DisplayRole)) sender = str(index.data(MessageList.SenderRole)) timestamp = index.data(MessageList.TimestampRole) + ts_text = timestamp.strftime(TIMESTAMP_FORMAT) is_same_sender = index.data(MessageList.IsSameSenderRole) alignment = index.data(Qt.ItemDataRole.TextAlignmentRole) + is_user = alignment & Qt.AlignmentFlag.AlignLeft + content_rect = option.rect.adjusted( self.padding, self.padding, -self.padding, -self.padding ) - current_y = content_rect.top() + + # Fonts base_font = QFont(option.font) + sender_font = QFont(option.font) + sender_font.setBold(True) + sender_font.setPointSize(base_font.pointSize() - 1) + ts_font = QFont(base_font) + ts_font.setPointSize(base_font.pointSize() - 3) + + max_width = option.rect.width() * 0.8 + inner_width = max_width - 2 * self.padding + + metrics = QFontMetrics(base_font) + sender_metrics = QFontMetrics(sender_font) + ts_metrics = QFontMetrics(ts_font) + + text_rect = metrics.boundingRect( + 0, 0, inner_width, 10000, Qt.TextFlag.TextWordWrap, text + ) + + # Bubble Dimensions + bubble_height = text_rect.height() + self.padding * 2 + bubble_width = text_rect.width() + if not is_same_sender: + bubble_height += self.sender_height + bubble_width = max(bubble_width, sender_metrics.horizontalAdvance(sender)) + bubble_height += ts_metrics.height() + bubble_width = max(bubble_width, ts_metrics.horizontalAdvance(ts_text)) + bubble_width += 2 * self.padding + + # Bubble Position + if alignment & Qt.AlignmentFlag.AlignLeft: + bubble_left = content_rect.left() - self.padding + bubble_color = OTHER_COLOR + else: + bubble_left = content_rect.right() - bubble_width - self.padding + bubble_color = USER_COLOR + bubble_rect = QRect( + bubble_left, option.rect.top() + self.spacing, bubble_width, bubble_height + ) + + painter.setRenderHint(painter.RenderHint.Antialiasing) + + # Paint Bubble + painter.setBrush(QColor(bubble_color)) + pen_color = TEXT_COLOR if is_user else USER_TEXT_COLOR + painter.setPen(QColor(pen_color)) + painter.drawRoundedRect(bubble_rect, 10, 10) + + # Text Positioning + current_top = bubble_rect.top() + self.padding + current_left = bubble_rect.left() + self.padding + current_width = bubble_rect.width() - 2 * self.padding + + # Sender if not is_same_sender: - sender_font = QFont(option.font) - sender_font.setBold(True) - sender_font.setPointSize(base_font.pointSize() - 1) painter.setFont(sender_font) - sender_rect = QRect( - content_rect.left(), - current_y, - content_rect.width(), - content_rect.height(), + painter.drawText( + current_left, + current_top, + current_width, + self.sender_height, + alignment, + sender, ) - painter.drawText(sender_rect, alignment, sender) - current_y += self.sender_height + self.spacing + current_top += self.sender_height + # Message Text painter.setFont(option.font) - message_rect = QRect( - content_rect.left(), - current_y, - content_rect.width(), - content_rect.height() - (current_y - content_rect.top()) + self.spacing, + painter.drawText( + current_left, + current_top, + current_width, + text_rect.height(), + alignment | Qt.TextFlag.TextWordWrap, + text, ) + current_top += text_rect.height() - painter.drawText(message_rect, alignment | Qt.TextFlag.TextWordWrap, text) - - ts_font = QFont(base_font) - ts_font.setPointSize(base_font.pointSize() - 3) - ts = timestamp.strftime("%Y-%m-%d %H:%M") - ts_rect = QRect( - content_rect.left(), - current_y + message_rect.height(), - content_rect.width(), - content_rect.height(), - ) + # Timestamp painter.setFont(ts_font) - painter.drawText(ts_rect, alignment, ts) + ts_color = TIMESTAMP_COLOR if is_user else USER_TIMESTAMP_COLOR + painter.setPen(QColor(ts_color)) + painter.drawText( + current_left, + current_top, + current_width, + ts_metrics.height(), + alignment, + ts_text, + ) painter.restore() def sizeHint(self, option, index): text = str(index.data(Qt.ItemDataRole.DisplayRole)) is_same_sender = index.data(MessageList.IsSameSenderRole) - width = option.rect.width() - available_width = width - (self.padding * 2) + max_width = option.rect.width() * 0.8 + inner_width = max_width - 2 * self.padding + base_font = QFont(option.font) + ts_font = QFont(base_font) + ts_font.setPointSize(base_font.pointSize() - 3) + + metrics = QFontMetrics(base_font) + ts_metrics = QFontMetrics(ts_font) - metrics = QFontMetrics(option.font) text_rect = metrics.boundingRect( - 0, 0, available_width, 10000, Qt.TextFlag.TextWordWrap, text + 0, 0, inner_width, 10000, Qt.TextFlag.TextWordWrap, text ) - height = text_rect.height() + (self.padding * 2) + height = text_rect.height() + self.padding * 2 + ts_metrics.height() if not is_same_sender: height += self.sender_height - height += self.spacing - return QSize(width, height) + + return QSize(option.rect.width(), height + self.spacing)