|
|
|
|
@ -37,10 +37,18 @@ class MessageBubble(QFrame):
|
|
|
|
|
layout.setContentsMargins(10, 5, 10, 5)
|
|
|
|
|
layout.setSpacing(2)
|
|
|
|
|
|
|
|
|
|
# 消息内容
|
|
|
|
|
content_label = QLabel(self.message.content)
|
|
|
|
|
content_label.setWordWrap(True)
|
|
|
|
|
content_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
|
|
|
# 根据消息类型显示不同内容
|
|
|
|
|
if self.message.content_type == MessageType.IMAGE:
|
|
|
|
|
# 图片消息 - 显示缩略图
|
|
|
|
|
content_widget = self._create_image_widget()
|
|
|
|
|
elif self.message.content_type == MessageType.FILE_REQUEST:
|
|
|
|
|
# 文件消息 - 显示文件信息
|
|
|
|
|
content_widget = self._create_file_widget()
|
|
|
|
|
else:
|
|
|
|
|
# 文本消息
|
|
|
|
|
content_widget = QLabel(self.message.content)
|
|
|
|
|
content_widget.setWordWrap(True)
|
|
|
|
|
content_widget.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
|
|
|
|
|
|
|
|
# 时间戳
|
|
|
|
|
time_str = self.message.timestamp.strftime("%H:%M")
|
|
|
|
|
@ -68,7 +76,7 @@ class MessageBubble(QFrame):
|
|
|
|
|
bottom_layout.addWidget(time_label)
|
|
|
|
|
bottom_layout.addWidget(status_label)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(content_label)
|
|
|
|
|
layout.addWidget(content_widget)
|
|
|
|
|
layout.addLayout(bottom_layout)
|
|
|
|
|
else:
|
|
|
|
|
# 对方发送的消息,左对齐
|
|
|
|
|
@ -82,8 +90,110 @@ class MessageBubble(QFrame):
|
|
|
|
|
""")
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(content_label)
|
|
|
|
|
layout.addWidget(content_widget)
|
|
|
|
|
layout.addWidget(time_label)
|
|
|
|
|
|
|
|
|
|
def _create_image_widget(self) -> QWidget:
|
|
|
|
|
"""创建图片显示组件"""
|
|
|
|
|
import os
|
|
|
|
|
from PyQt6.QtGui import QPixmap
|
|
|
|
|
|
|
|
|
|
container = QWidget()
|
|
|
|
|
layout = QVBoxLayout(container)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
file_path = self.message.content
|
|
|
|
|
file_name = os.path.basename(file_path)
|
|
|
|
|
|
|
|
|
|
# 尝试加载图片
|
|
|
|
|
if os.path.exists(file_path):
|
|
|
|
|
pixmap = QPixmap(file_path)
|
|
|
|
|
if not pixmap.isNull():
|
|
|
|
|
# 缩放图片
|
|
|
|
|
scaled = pixmap.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio,
|
|
|
|
|
Qt.TransformationMode.SmoothTransformation)
|
|
|
|
|
image_label = QLabel()
|
|
|
|
|
image_label.setPixmap(scaled)
|
|
|
|
|
image_label.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
|
|
|
image_label.setToolTip(f"点击打开: {file_path}")
|
|
|
|
|
image_label.mousePressEvent = lambda e: self._open_file(file_path)
|
|
|
|
|
layout.addWidget(image_label)
|
|
|
|
|
else:
|
|
|
|
|
layout.addWidget(QLabel(f"[图片] {file_name}"))
|
|
|
|
|
else:
|
|
|
|
|
layout.addWidget(QLabel(f"[图片] {file_name}\n(文件不存在)"))
|
|
|
|
|
|
|
|
|
|
return container
|
|
|
|
|
|
|
|
|
|
def _create_file_widget(self) -> QWidget:
|
|
|
|
|
"""创建文件显示组件"""
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
container = QWidget()
|
|
|
|
|
layout = QHBoxLayout(container)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
file_path = self.message.content
|
|
|
|
|
file_name = os.path.basename(file_path)
|
|
|
|
|
|
|
|
|
|
# 文件图标
|
|
|
|
|
icon_label = QLabel("📄")
|
|
|
|
|
icon_label.setStyleSheet("font-size: 24px;")
|
|
|
|
|
layout.addWidget(icon_label)
|
|
|
|
|
|
|
|
|
|
# 文件信息
|
|
|
|
|
info_layout = QVBoxLayout()
|
|
|
|
|
name_label = QLabel(file_name)
|
|
|
|
|
name_label.setStyleSheet("font-weight: bold;")
|
|
|
|
|
info_layout.addWidget(name_label)
|
|
|
|
|
|
|
|
|
|
if os.path.exists(file_path):
|
|
|
|
|
size = os.path.getsize(file_path)
|
|
|
|
|
size_str = self._format_size(size)
|
|
|
|
|
size_label = QLabel(size_str)
|
|
|
|
|
size_label.setStyleSheet("color: #666; font-size: 11px;")
|
|
|
|
|
info_layout.addWidget(size_label)
|
|
|
|
|
else:
|
|
|
|
|
info_layout.addWidget(QLabel("(文件不存在)"))
|
|
|
|
|
|
|
|
|
|
layout.addLayout(info_layout)
|
|
|
|
|
|
|
|
|
|
# 点击打开
|
|
|
|
|
container.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
|
|
|
container.setToolTip(f"点击打开: {file_path}")
|
|
|
|
|
container.mousePressEvent = lambda e: self._open_file(file_path)
|
|
|
|
|
|
|
|
|
|
return container
|
|
|
|
|
|
|
|
|
|
def _format_size(self, size: int) -> str:
|
|
|
|
|
"""格式化文件大小"""
|
|
|
|
|
if size < 1024:
|
|
|
|
|
return f"{size} B"
|
|
|
|
|
elif size < 1024 * 1024:
|
|
|
|
|
return f"{size / 1024:.1f} KB"
|
|
|
|
|
elif size < 1024 * 1024 * 1024:
|
|
|
|
|
return f"{size / (1024 * 1024):.1f} MB"
|
|
|
|
|
else:
|
|
|
|
|
return f"{size / (1024 * 1024 * 1024):.1f} GB"
|
|
|
|
|
|
|
|
|
|
def _open_file(self, file_path: str):
|
|
|
|
|
"""打开文件"""
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(file_path):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if sys.platform == 'win32':
|
|
|
|
|
os.startfile(file_path)
|
|
|
|
|
elif sys.platform == 'darwin':
|
|
|
|
|
subprocess.run(['open', file_path])
|
|
|
|
|
else:
|
|
|
|
|
subprocess.run(['xdg-open', file_path])
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Failed to open file: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChatWidget(QWidget):
|
|
|
|
|
|