mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 02:22:26 +08:00
✨ 簡化 gui 設置版面
This commit is contained in:
parent
00bbf1e113
commit
a72a858444
@ -12,6 +12,7 @@ from PySide6.QtWidgets import (
|
||||
QComboBox, QRadioButton, QButtonGroup, QMessageBox,
|
||||
QCheckBox, QPushButton, QFrame
|
||||
)
|
||||
from ..widgets import SwitchWithLabel
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
@ -152,11 +153,6 @@ class SettingsTab(QWidget):
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['language_header'] = header
|
||||
|
||||
desc = self._create_description(t('settings.language.description'))
|
||||
layout.addWidget(desc)
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['language_desc'] = desc
|
||||
|
||||
# 語言選擇器容器
|
||||
lang_container = QHBoxLayout()
|
||||
lang_container.setContentsMargins(0, 0, 0, 0)
|
||||
@ -210,11 +206,6 @@ class SettingsTab(QWidget):
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['layout_header'] = header
|
||||
|
||||
desc = self._create_description(t('settings.layout.description'))
|
||||
layout.addWidget(desc)
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['layout_desc'] = desc
|
||||
|
||||
# 選項容器
|
||||
options_layout = QVBoxLayout()
|
||||
options_layout.setSpacing(2)
|
||||
@ -304,60 +295,15 @@ class SettingsTab(QWidget):
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['window_header'] = header
|
||||
|
||||
desc = self._create_description(t('settings.window.alwaysCenterDescription'))
|
||||
layout.addWidget(desc)
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['window_desc'] = desc
|
||||
|
||||
# 選項容器
|
||||
options_layout = QVBoxLayout()
|
||||
options_layout.setSpacing(2)
|
||||
options_layout.setSpacing(8)
|
||||
|
||||
self.always_center_checkbox = QCheckBox(t('settings.window.alwaysCenter'))
|
||||
self.always_center_checkbox.setChecked(self.config_manager.get_always_center_window())
|
||||
self.always_center_checkbox.setStyleSheet("""
|
||||
QCheckBox {
|
||||
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
spacing: 8px;
|
||||
padding: 2px 0px;
|
||||
}
|
||||
QCheckBox::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
QCheckBox::indicator:unchecked {
|
||||
border: 2px solid #666666;
|
||||
border-radius: 3px;
|
||||
background-color: transparent;
|
||||
}
|
||||
QCheckBox::indicator:checked {
|
||||
border: 2px solid #0078d4;
|
||||
border-radius: 3px;
|
||||
background-color: #0078d4;
|
||||
image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwIDNMNC41IDguNUwyIDYiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+);
|
||||
}
|
||||
QCheckBox::indicator:hover {
|
||||
border-color: #0078d4;
|
||||
}
|
||||
""")
|
||||
self.always_center_checkbox.stateChanged.connect(self._on_always_center_changed)
|
||||
options_layout.addWidget(self.always_center_checkbox)
|
||||
|
||||
center_hint = QLabel(f" {t('settings.window.alwaysCenterDescription')}")
|
||||
center_hint.setStyleSheet("""
|
||||
QLabel {
|
||||
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
|
||||
color: #888888;
|
||||
font-size: 11px;
|
||||
margin-left: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
""")
|
||||
options_layout.addWidget(center_hint)
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['center_hint'] = center_hint
|
||||
# 使用現代化的 Switch 組件
|
||||
self.always_center_switch = SwitchWithLabel(t('settings.window.alwaysCenter'))
|
||||
self.always_center_switch.setChecked(self.config_manager.get_always_center_window())
|
||||
self.always_center_switch.toggled.connect(self._on_always_center_changed)
|
||||
options_layout.addWidget(self.always_center_switch)
|
||||
|
||||
layout.addLayout(options_layout)
|
||||
|
||||
@ -368,11 +314,6 @@ class SettingsTab(QWidget):
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['reset_header'] = header
|
||||
|
||||
desc = self._create_description(t('settings.reset.description'))
|
||||
layout.addWidget(desc)
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['reset_desc'] = desc
|
||||
|
||||
reset_container = QHBoxLayout()
|
||||
reset_container.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
@ -475,12 +416,11 @@ class SettingsTab(QWidget):
|
||||
# 發出佈局變更請求信號
|
||||
self.layout_change_requested.emit(new_combined_mode, new_orientation)
|
||||
|
||||
def _on_always_center_changed(self, state: int) -> None:
|
||||
def _on_always_center_changed(self, checked: bool) -> None:
|
||||
"""視窗定位選項變更事件處理"""
|
||||
always_center = state == Qt.CheckState.Checked.value
|
||||
# 立即保存設定
|
||||
self.config_manager.set_always_center_window(always_center)
|
||||
debug_log(f"視窗定位設置已保存: {always_center}") # 調試輸出
|
||||
self.config_manager.set_always_center_window(checked)
|
||||
debug_log(f"視窗定位設置已保存: {checked}") # 調試輸出
|
||||
|
||||
def _on_reset_settings(self) -> None:
|
||||
"""重置設定事件處理"""
|
||||
@ -507,15 +447,7 @@ class SettingsTab(QWidget):
|
||||
if 'reset_header' in self.ui_elements:
|
||||
self.ui_elements['reset_header'].setText(f"🔄 {t('settings.reset.title')}")
|
||||
|
||||
# 更新描述文字
|
||||
if 'language_desc' in self.ui_elements:
|
||||
self.ui_elements['language_desc'].setText(t('settings.language.description'))
|
||||
if 'layout_desc' in self.ui_elements:
|
||||
self.ui_elements['layout_desc'].setText(t('settings.layout.description'))
|
||||
if 'window_desc' in self.ui_elements:
|
||||
self.ui_elements['window_desc'].setText(t('settings.window.alwaysCenterDescription'))
|
||||
if 'reset_desc' in self.ui_elements:
|
||||
self.ui_elements['reset_desc'].setText(t('settings.reset.description'))
|
||||
|
||||
|
||||
# 更新提示文字
|
||||
if 'separate_hint' in self.ui_elements:
|
||||
@ -524,16 +456,14 @@ class SettingsTab(QWidget):
|
||||
self.ui_elements['vertical_hint'].setText(f" {t('settings.layout.combinedVerticalDescription')}")
|
||||
if 'horizontal_hint' in self.ui_elements:
|
||||
self.ui_elements['horizontal_hint'].setText(f" {t('settings.layout.combinedHorizontalDescription')}")
|
||||
if 'center_hint' in self.ui_elements:
|
||||
self.ui_elements['center_hint'].setText(f" {t('settings.window.alwaysCenterDescription')}")
|
||||
|
||||
# 更新按鈕文字
|
||||
if hasattr(self, 'reset_button'):
|
||||
self.reset_button.setText(t('settings.reset.button'))
|
||||
|
||||
# 更新複選框文字
|
||||
if hasattr(self, 'always_center_checkbox'):
|
||||
self.always_center_checkbox.setText(t('settings.window.alwaysCenter'))
|
||||
# 更新切換開關文字
|
||||
if hasattr(self, 'always_center_switch'):
|
||||
self.always_center_switch.setText(t('settings.window.alwaysCenter'))
|
||||
|
||||
# 更新單選按鈕文字
|
||||
if hasattr(self, 'separate_mode_radio'):
|
||||
@ -557,9 +487,9 @@ class SettingsTab(QWidget):
|
||||
self._set_initial_layout_state()
|
||||
|
||||
# 重新載入視窗設定
|
||||
if hasattr(self, 'always_center_checkbox'):
|
||||
if hasattr(self, 'always_center_switch'):
|
||||
always_center = self.config_manager.get_always_center_window()
|
||||
self.always_center_checkbox.setChecked(always_center)
|
||||
self.always_center_switch.setChecked(always_center)
|
||||
debug_log(f"重新載入視窗定位設置: {always_center}") # 調試輸出
|
||||
|
||||
def set_layout_mode(self, combined_mode: bool) -> None:
|
||||
|
@ -8,9 +8,12 @@ GUI 自定義元件模組
|
||||
from .text_edit import SmartTextEdit
|
||||
from .image_preview import ImagePreviewWidget
|
||||
from .image_upload import ImageUploadWidget
|
||||
from .switch import SwitchWidget, SwitchWithLabel
|
||||
|
||||
__all__ = [
|
||||
'SmartTextEdit',
|
||||
'ImagePreviewWidget',
|
||||
'ImageUploadWidget'
|
||||
'ImageUploadWidget',
|
||||
'SwitchWidget',
|
||||
'SwitchWithLabel'
|
||||
]
|
237
src/mcp_feedback_enhanced/gui/widgets/switch.py
Normal file
237
src/mcp_feedback_enhanced/gui/widgets/switch.py
Normal file
@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
現代化切換開關組件
|
||||
==================
|
||||
|
||||
提供類似 web 的現代化 switch 切換開關。
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import QWidget, QHBoxLayout, QLabel, QToolTip
|
||||
from PySide6.QtCore import Signal, QPropertyAnimation, QRect, QEasingCurve, Property, Qt, QTimer
|
||||
from PySide6.QtGui import QPainter, QColor, QPainterPath, QFont
|
||||
|
||||
|
||||
class SwitchWidget(QWidget):
|
||||
"""現代化切換開關組件"""
|
||||
|
||||
toggled = Signal(bool) # 狀態變更信號
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# 狀態變數
|
||||
self._checked = False
|
||||
self._enabled = True
|
||||
self._animating = False
|
||||
|
||||
# 尺寸設定
|
||||
self._width = 50
|
||||
self._height = 24
|
||||
self._thumb_radius = 10
|
||||
self._track_radius = 12
|
||||
|
||||
# 顏色設定
|
||||
self._track_color_off = QColor(102, 102, 102) # #666666
|
||||
self._track_color_on = QColor(0, 120, 212) # #0078d4
|
||||
self._thumb_color = QColor(255, 255, 255) # white
|
||||
self._track_color_disabled = QColor(68, 68, 68) # #444444
|
||||
|
||||
# 動畫屬性
|
||||
self._thumb_position = 2.0
|
||||
|
||||
# 設定基本屬性
|
||||
self.setFixedSize(self._width, self._height)
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
# 創建動畫
|
||||
self._animation = QPropertyAnimation(self, b"thumbPosition")
|
||||
self._animation.setDuration(200) # 200ms 動畫時間
|
||||
self._animation.setEasingCurve(QEasingCurve.OutCubic)
|
||||
|
||||
# 設置工具提示延遲
|
||||
self._tooltip_timer = QTimer()
|
||||
self._tooltip_timer.setSingleShot(True)
|
||||
self._tooltip_timer.timeout.connect(self._show_delayed_tooltip)
|
||||
|
||||
@Property(float)
|
||||
def thumbPosition(self):
|
||||
return self._thumb_position
|
||||
|
||||
@thumbPosition.setter
|
||||
def thumbPosition(self, position):
|
||||
self._thumb_position = position
|
||||
self.update()
|
||||
|
||||
def isChecked(self) -> bool:
|
||||
"""獲取選中狀態"""
|
||||
return self._checked
|
||||
|
||||
def setChecked(self, checked: bool) -> None:
|
||||
"""設置選中狀態"""
|
||||
if self._checked != checked:
|
||||
self._checked = checked
|
||||
self._animate_to_position()
|
||||
self.toggled.emit(checked)
|
||||
|
||||
def setEnabled(self, enabled: bool) -> None:
|
||||
"""設置啟用狀態"""
|
||||
super().setEnabled(enabled)
|
||||
self._enabled = enabled
|
||||
self.setCursor(Qt.PointingHandCursor if enabled else Qt.ArrowCursor)
|
||||
self.update()
|
||||
|
||||
def _animate_to_position(self) -> None:
|
||||
"""動畫到目標位置"""
|
||||
if self._animating:
|
||||
return
|
||||
|
||||
self._animating = True
|
||||
target_position = self._width - self._thumb_radius * 2 - 2 if self._checked else 2
|
||||
|
||||
self._animation.setStartValue(self._thumb_position)
|
||||
self._animation.setEndValue(target_position)
|
||||
self._animation.finished.connect(self._on_animation_finished)
|
||||
self._animation.start()
|
||||
|
||||
def _on_animation_finished(self) -> None:
|
||||
"""動畫完成處理"""
|
||||
self._animating = False
|
||||
self._animation.finished.disconnect()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""滑鼠按下事件"""
|
||||
if event.button() == Qt.LeftButton and self._enabled:
|
||||
self.setChecked(not self._checked)
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def enterEvent(self, event):
|
||||
"""滑鼠進入事件"""
|
||||
if self._enabled:
|
||||
# 延遲顯示工具提示
|
||||
self._tooltip_timer.start(500) # 500ms 延遲
|
||||
super().enterEvent(event)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
"""滑鼠離開事件"""
|
||||
self._tooltip_timer.stop()
|
||||
super().leaveEvent(event)
|
||||
|
||||
def _show_delayed_tooltip(self):
|
||||
"""顯示延遲的工具提示"""
|
||||
if self.toolTip():
|
||||
QToolTip.showText(self.mapToGlobal(self.rect().center()), self.toolTip(), self)
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""繪製事件"""
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
|
||||
# 計算軌道矩形
|
||||
track_rect = QRect(0, (self._height - self._track_radius * 2) // 2,
|
||||
self._width, self._track_radius * 2)
|
||||
|
||||
# 繪製軌道
|
||||
track_path = QPainterPath()
|
||||
track_path.addRoundedRect(track_rect, self._track_radius, self._track_radius)
|
||||
|
||||
if not self._enabled:
|
||||
track_color = self._track_color_disabled
|
||||
elif self._checked:
|
||||
track_color = self._track_color_on
|
||||
else:
|
||||
track_color = self._track_color_off
|
||||
|
||||
painter.fillPath(track_path, track_color)
|
||||
|
||||
# 繪製滑塊
|
||||
thumb_x = self._thumb_position
|
||||
thumb_y = (self._height - self._thumb_radius * 2) // 2
|
||||
thumb_rect = QRect(int(thumb_x), thumb_y, self._thumb_radius * 2, self._thumb_radius * 2)
|
||||
|
||||
thumb_path = QPainterPath()
|
||||
thumb_path.addEllipse(thumb_rect)
|
||||
|
||||
# 滑塊顏色(可以根據狀態調整透明度)
|
||||
thumb_color = self._thumb_color
|
||||
if not self._enabled:
|
||||
thumb_color.setAlpha(180) # 半透明效果
|
||||
|
||||
painter.fillPath(thumb_path, thumb_color)
|
||||
|
||||
# 添加微妙的陰影效果
|
||||
if self._enabled:
|
||||
shadow_color = QColor(0, 0, 0, 30)
|
||||
shadow_rect = thumb_rect.translated(0, 1)
|
||||
shadow_path = QPainterPath()
|
||||
shadow_path.addEllipse(shadow_rect)
|
||||
painter.fillPath(shadow_path, shadow_color)
|
||||
|
||||
|
||||
class SwitchWithLabel(QWidget):
|
||||
"""帶標籤的切換開關組件"""
|
||||
|
||||
toggled = Signal(bool)
|
||||
|
||||
def __init__(self, text: str = "", parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# 創建布局
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(12)
|
||||
|
||||
# 創建標籤
|
||||
self.label = QLabel(text)
|
||||
self.label.setStyleSheet("""
|
||||
QLabel {
|
||||
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
}
|
||||
""")
|
||||
|
||||
# 創建切換開關
|
||||
self.switch = SwitchWidget()
|
||||
self.switch.toggled.connect(self.toggled.emit)
|
||||
|
||||
# 添加到布局
|
||||
layout.addWidget(self.label)
|
||||
layout.addStretch() # 彈性空間,將開關推到右側
|
||||
layout.addWidget(self.switch)
|
||||
|
||||
# 設置點擊標籤也能切換開關
|
||||
self.label.mousePressEvent = self._on_label_clicked
|
||||
|
||||
def _on_label_clicked(self, event):
|
||||
"""標籤點擊事件"""
|
||||
if event.button() == Qt.LeftButton:
|
||||
self.switch.setChecked(not self.switch.isChecked())
|
||||
|
||||
def setText(self, text: str) -> None:
|
||||
"""設置標籤文字"""
|
||||
self.label.setText(text)
|
||||
|
||||
def text(self) -> str:
|
||||
"""獲取標籤文字"""
|
||||
return self.label.text()
|
||||
|
||||
def isChecked(self) -> bool:
|
||||
"""獲取選中狀態"""
|
||||
return self.switch.isChecked()
|
||||
|
||||
def setChecked(self, checked: bool) -> None:
|
||||
"""設置選中狀態"""
|
||||
self.switch.setChecked(checked)
|
||||
|
||||
def setEnabled(self, enabled: bool) -> None:
|
||||
"""設置啟用狀態"""
|
||||
super().setEnabled(enabled)
|
||||
self.switch.setEnabled(enabled)
|
||||
self.label.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
|
||||
font-size: 13px;
|
||||
color: {"#ffffff" if enabled else "#888888"};
|
||||
}}
|
||||
""")
|
@ -87,12 +87,10 @@
|
||||
"title": "Application Settings",
|
||||
"language": {
|
||||
"title": "Language Settings",
|
||||
"selector": "🌐 Language Selection",
|
||||
"description": "Choose your preferred interface language. Language changes take effect immediately."
|
||||
"selector": "🌐 Language Selection"
|
||||
},
|
||||
"layout": {
|
||||
"title": "Interface Layout",
|
||||
"description": "Choose the display method for AI summary and feedback input areas",
|
||||
"separateMode": "Separate Mode",
|
||||
"separateModeDescription": "AI summary and feedback are in separate tabs",
|
||||
"combinedVertical": "Combined Mode (Vertical Layout)",
|
||||
@ -102,12 +100,10 @@
|
||||
},
|
||||
"window": {
|
||||
"title": "Window Positioning",
|
||||
"alwaysCenter": "Always show window at primary screen center",
|
||||
"alwaysCenterDescription": "Recommended for multi-monitor setups or when experiencing window positioning issues"
|
||||
"alwaysCenter": "Always show window at primary screen center"
|
||||
},
|
||||
"reset": {
|
||||
"title": "Reset Settings",
|
||||
"description": "Clear all saved settings and restore to default state",
|
||||
"button": "Reset Settings",
|
||||
"confirmTitle": "Confirm Reset Settings",
|
||||
"confirmMessage": "Are you sure you want to reset all settings? This will clear all saved preferences and restore to default state.",
|
||||
|
@ -67,12 +67,10 @@
|
||||
"title": "应用设置",
|
||||
"language": {
|
||||
"title": "语言设置",
|
||||
"selector": "🌐 语言选择",
|
||||
"description": "选择您偏好的界面语言。语言更改会立即生效。"
|
||||
"selector": "🌐 语言选择"
|
||||
},
|
||||
"layout": {
|
||||
"title": "界面布局",
|
||||
"description": "选择 AI 摘要和反馈输入区域的显示方式",
|
||||
"separateMode": "分离模式",
|
||||
"separateModeDescription": "AI 摘要和反馈分别在不同页签",
|
||||
"combinedVertical": "合并模式(垂直布局)",
|
||||
@ -82,12 +80,10 @@
|
||||
},
|
||||
"window": {
|
||||
"title": "窗口定位",
|
||||
"alwaysCenter": "总是在主屏幕中心显示窗口",
|
||||
"alwaysCenterDescription": "建议在多屏幕环境或遇到窗口定位问题时开启此选项"
|
||||
"alwaysCenter": "总是在主屏幕中心显示窗口"
|
||||
},
|
||||
"reset": {
|
||||
"title": "重置设置",
|
||||
"description": "清除所有已保存的设置,恢复到默认状态",
|
||||
"button": "重置设置",
|
||||
"confirmTitle": "确认重置设置",
|
||||
"confirmMessage": "确定要重置所有设置吗?这将清除所有已保存的偏好设置并恢复到默认状态。",
|
||||
|
@ -83,12 +83,10 @@
|
||||
"title": "應用設置",
|
||||
"language": {
|
||||
"title": "語言設置",
|
||||
"selector": "🌐 語言選擇",
|
||||
"description": "選擇您偏好的界面語言。語言變更會立即生效。"
|
||||
"selector": "🌐 語言選擇"
|
||||
},
|
||||
"layout": {
|
||||
"title": "界面佈局",
|
||||
"description": "選擇 AI 摘要和回饋輸入區域的顯示方式",
|
||||
"separateMode": "分離模式",
|
||||
"separateModeDescription": "AI 摘要和回饋分別在不同頁籤",
|
||||
"combinedVertical": "合併模式(垂直布局)",
|
||||
@ -98,12 +96,10 @@
|
||||
},
|
||||
"window": {
|
||||
"title": "視窗定位",
|
||||
"alwaysCenter": "總是在主螢幕中心顯示視窗",
|
||||
"alwaysCenterDescription": "建議在多螢幕環境或遇到視窗定位問題時開啟此選項"
|
||||
"alwaysCenter": "總是在主螢幕中心顯示視窗"
|
||||
},
|
||||
"reset": {
|
||||
"title": "重置設定",
|
||||
"description": "清除所有已保存的設定,恢復到預設狀態",
|
||||
"button": "重置設定",
|
||||
"confirmTitle": "確認重置設定",
|
||||
"confirmMessage": "確定要重置所有設定嗎?這將清除所有已保存的偏好設定並恢復到預設狀態。",
|
||||
|
Loading…
x
Reference in New Issue
Block a user