diff --git a/src/mcp_feedback_enhanced/gui/tabs/settings_tab.py b/src/mcp_feedback_enhanced/gui/tabs/settings_tab.py index 4d867e4..990d573 100644 --- a/src/mcp_feedback_enhanced/gui/tabs/settings_tab.py +++ b/src/mcp_feedback_enhanced/gui/tabs/settings_tab.py @@ -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: diff --git a/src/mcp_feedback_enhanced/gui/widgets/__init__.py b/src/mcp_feedback_enhanced/gui/widgets/__init__.py index d242fea..7ccf5d3 100644 --- a/src/mcp_feedback_enhanced/gui/widgets/__init__.py +++ b/src/mcp_feedback_enhanced/gui/widgets/__init__.py @@ -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' ] \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/gui/widgets/switch.py b/src/mcp_feedback_enhanced/gui/widgets/switch.py new file mode 100644 index 0000000..ed8531c --- /dev/null +++ b/src/mcp_feedback_enhanced/gui/widgets/switch.py @@ -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"}; + }} + """) \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/locales/en/translations.json b/src/mcp_feedback_enhanced/locales/en/translations.json index 3f4b1c3..9d06ac3 100644 --- a/src/mcp_feedback_enhanced/locales/en/translations.json +++ b/src/mcp_feedback_enhanced/locales/en/translations.json @@ -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.", diff --git a/src/mcp_feedback_enhanced/locales/zh-CN/translations.json b/src/mcp_feedback_enhanced/locales/zh-CN/translations.json index cb4ed66..17428de 100644 --- a/src/mcp_feedback_enhanced/locales/zh-CN/translations.json +++ b/src/mcp_feedback_enhanced/locales/zh-CN/translations.json @@ -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": "确定要重置所有设置吗?这将清除所有已保存的偏好设置并恢复到默认状态。", diff --git a/src/mcp_feedback_enhanced/locales/zh-TW/translations.json b/src/mcp_feedback_enhanced/locales/zh-TW/translations.json index b2c65c7..3c093d8 100644 --- a/src/mcp_feedback_enhanced/locales/zh-TW/translations.json +++ b/src/mcp_feedback_enhanced/locales/zh-TW/translations.json @@ -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": "確定要重置所有設定嗎?這將清除所有已保存的偏好設定並恢復到預設狀態。",