diff --git a/src/mcp_feedback_enhanced/gui/locales/en/translations.json b/src/mcp_feedback_enhanced/gui/locales/en/translations.json index ebde0b5..600b449 100644 --- a/src/mcp_feedback_enhanced/gui/locales/en/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/en/translations.json @@ -117,7 +117,8 @@ }, "window": { "title": "Window Positioning", - "alwaysCenter": "Always show window at primary screen center" + "alwaysCenter": "Always show window at primary screen center", + "autoFocus": "Auto-focus input box when window opens" }, "reset": { "title": "Reset Settings", @@ -357,4 +358,4 @@ "findPortFailed": "❌ Failed to find available port: {error}" } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json b/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json index f415dd9..51cd28b 100644 --- a/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json @@ -97,7 +97,8 @@ }, "window": { "title": "窗口定位", - "alwaysCenter": "总是在主屏幕中心显示窗口" + "alwaysCenter": "总是在主屏幕中心显示窗口", + "autoFocus": "窗口打开时自动聚焦到输入框" }, "reset": { "title": "重置设置", @@ -352,4 +353,4 @@ "thanks": "致谢与贡献", "thanksText": "感谢原作者 Fábio Ferreira (@fabiomlferreira) 创建了原始的 interactive-feedback-mcp 项目。\n\n本增强版本由 Minidoracat 开发和维护,大幅扩展了项目功能,新增了 GUI 界面、图片支持、多语言能力以及许多其他改进功能。\n\n同时感谢 sanshao85 的 mcp-feedback-collector 项目提供的 UI 设计灵感。\n\n开源协作让技术变得更美好!" } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json b/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json index 80dc117..af87f63 100644 --- a/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json @@ -129,7 +129,8 @@ }, "window": { "title": "視窗定位", - "alwaysCenter": "總是在主螢幕中心顯示視窗" + "alwaysCenter": "總是在主螢幕中心顯示視窗", + "autoFocus": "窗口打開時自動聚焦到輸入框" }, "reset": { "title": "重置設定", @@ -352,4 +353,4 @@ "findPortFailed": "❌ 尋找可用端口失敗: {error}" } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/gui/tabs/settings_tab.py b/src/mcp_feedback_enhanced/gui/tabs/settings_tab.py index 94f5147..d1f3ecb 100644 --- a/src/mcp_feedback_enhanced/gui/tabs/settings_tab.py +++ b/src/mcp_feedback_enhanced/gui/tabs/settings_tab.py @@ -27,65 +27,65 @@ class SettingsTab(QWidget): layout_change_requested = Signal(bool, str) # 佈局變更請求信號 (combined_mode, orientation) reset_requested = Signal() # 重置設定請求信號 timeout_settings_changed = Signal(bool, int) # 超時設置變更信號 (enabled, duration) - + def __init__(self, combined_mode: bool, config_manager, parent=None): super().__init__(parent) self.combined_mode = combined_mode self.config_manager = config_manager self.layout_orientation = self.config_manager.get_layout_orientation() self.i18n = get_i18n_manager() - + # 保存需要更新的UI元素引用 self.ui_elements = {} - + # 設置全域字體為微軟正黑體 self._setup_font() self._setup_ui() - + # 在UI設置完成後,確保正確設置初始狀態 self._set_initial_layout_state() - + def _setup_font(self) -> None: """設置全域字體""" font = QFont("Microsoft JhengHei", 9) # 微軟正黑體,調整為 9pt self.setFont(font) - + # 設置整個控件的樣式表,確保中文字體正確 self.setStyleSheet(""" QWidget { font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif; } """) - + def _setup_ui(self) -> None: """設置用戶介面""" # 主容器 main_layout = QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) - + # 左側內容區域 content_widget = QWidget() content_widget.setMaximumWidth(600) content_layout = QVBoxLayout(content_widget) content_layout.setContentsMargins(20, 20, 20, 20) content_layout.setSpacing(16) - + # === 語言設置 === self._create_language_section(content_layout) - + # 添加分隔線 self._add_separator(content_layout) - + # === 界面佈局 === self._create_layout_section(content_layout) - + # 添加分隔線 self._add_separator(content_layout) - + # === 視窗設置 === self._create_window_section(content_layout) - + # 添加分隔線 self._add_separator(content_layout) @@ -97,17 +97,17 @@ class SettingsTab(QWidget): # === 重置設定 === self._create_reset_section(content_layout) - + # 添加彈性空間 content_layout.addStretch() - + # 添加到主布局 main_layout.addWidget(content_widget) main_layout.addStretch() # 右側彈性空間 - + # 設定初始狀態 self._set_initial_layout_state() - + def _add_separator(self, layout: QVBoxLayout) -> None: """添加分隔線""" separator = QFrame() @@ -122,7 +122,7 @@ class SettingsTab(QWidget): } """) layout.addWidget(separator) - + def _create_section_header(self, title: str, emoji: str = "") -> QLabel: """創建區塊標題""" text = f"{emoji} {title}" if emoji else title @@ -138,7 +138,7 @@ class SettingsTab(QWidget): } """) return label - + def _create_description(self, text: str) -> QLabel: """創建說明文字""" label = QLabel(text) @@ -153,18 +153,18 @@ class SettingsTab(QWidget): """) label.setWordWrap(True) return label - + def _create_language_section(self, layout: QVBoxLayout) -> None: """創建語言設置區域""" header = self._create_section_header(t('settings.language.title'), "🌐") layout.addWidget(header) # 保存引用以便更新 self.ui_elements['language_header'] = header - + # 語言選擇器容器 lang_container = QHBoxLayout() lang_container.setContentsMargins(0, 0, 0, 0) - + self.language_selector = QComboBox() self.language_selector.setMinimumHeight(28) self.language_selector.setMaximumWidth(140) @@ -197,29 +197,29 @@ class SettingsTab(QWidget): min-width: 120px; } """) - + # 填充語言選項 self._populate_language_selector() self.language_selector.currentIndexChanged.connect(self._on_language_changed) - + lang_container.addWidget(self.language_selector) lang_container.addStretch() layout.addLayout(lang_container) - + def _create_layout_section(self, layout: QVBoxLayout) -> None: """創建界面佈局區域""" header = self._create_section_header(t('settings.layout.title'), "📐") layout.addWidget(header) # 保存引用以便更新 self.ui_elements['layout_header'] = header - + # 選項容器 options_layout = QVBoxLayout() options_layout.setSpacing(2) - + # 創建按鈕組 self.layout_button_group = QButtonGroup() - + # 分離模式 self.separate_mode_radio = QRadioButton(t('settings.layout.separateMode')) self.separate_mode_radio.setStyleSheet(""" @@ -251,7 +251,7 @@ class SettingsTab(QWidget): """) self.layout_button_group.addButton(self.separate_mode_radio, 0) options_layout.addWidget(self.separate_mode_radio) - + separate_hint = QLabel(f" {t('settings.layout.separateModeDescription')}") separate_hint.setStyleSheet(""" QLabel { @@ -265,53 +265,59 @@ class SettingsTab(QWidget): options_layout.addWidget(separate_hint) # 保存引用以便更新 self.ui_elements['separate_hint'] = separate_hint - + # 合併模式(垂直) self.combined_vertical_radio = QRadioButton(t('settings.layout.combinedVertical')) self.combined_vertical_radio.setStyleSheet(self.separate_mode_radio.styleSheet()) self.layout_button_group.addButton(self.combined_vertical_radio, 1) options_layout.addWidget(self.combined_vertical_radio) - + vertical_hint = QLabel(f" {t('settings.layout.combinedVerticalDescription')}") vertical_hint.setStyleSheet(separate_hint.styleSheet()) options_layout.addWidget(vertical_hint) # 保存引用以便更新 self.ui_elements['vertical_hint'] = vertical_hint - + # 合併模式(水平) self.combined_horizontal_radio = QRadioButton(t('settings.layout.combinedHorizontal')) self.combined_horizontal_radio.setStyleSheet(self.separate_mode_radio.styleSheet()) self.layout_button_group.addButton(self.combined_horizontal_radio, 2) options_layout.addWidget(self.combined_horizontal_radio) - + horizontal_hint = QLabel(f" {t('settings.layout.combinedHorizontalDescription')}") horizontal_hint.setStyleSheet(separate_hint.styleSheet()) options_layout.addWidget(horizontal_hint) # 保存引用以便更新 self.ui_elements['horizontal_hint'] = horizontal_hint - + layout.addLayout(options_layout) - + # 連接佈局變更信號 self.layout_button_group.buttonToggled.connect(self._on_layout_changed) - + def _create_window_section(self, layout: QVBoxLayout) -> None: """創建視窗設置區域""" header = self._create_section_header(t('settings.window.title'), "🖥️") layout.addWidget(header) # 保存引用以便更新 self.ui_elements['window_header'] = header - + # 選項容器 options_layout = QVBoxLayout() options_layout.setSpacing(8) - + # 使用現代化的 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) - + + # 自動聚焦開關 + self.auto_focus_switch = SwitchWithLabel(t('settings.window.autoFocus')) + self.auto_focus_switch.setChecked(self.config_manager.get_auto_focus_enabled()) + self.auto_focus_switch.toggled.connect(self._on_auto_focus_changed) + options_layout.addWidget(self.auto_focus_switch) + layout.addLayout(options_layout) def _create_timeout_section(self, layout: QVBoxLayout) -> None: @@ -376,10 +382,10 @@ class SettingsTab(QWidget): layout.addWidget(header) # 保存引用以便更新 self.ui_elements['reset_header'] = header - + reset_container = QHBoxLayout() reset_container.setContentsMargins(0, 0, 0, 0) - + self.reset_button = QPushButton(t('settings.reset.button')) self.reset_button.setMinimumHeight(32) self.reset_button.setMaximumWidth(110) @@ -402,7 +408,7 @@ class SettingsTab(QWidget): } """) self.reset_button.clicked.connect(self._on_reset_settings) - + reset_container.addWidget(self.reset_button) reset_container.addStretch() layout.addLayout(reset_container) @@ -414,28 +420,28 @@ class SettingsTab(QWidget): ('zh-CN', '简体中文'), ('en', 'English') ] - + current_language = self.i18n.get_current_language() - + # 暫時斷開信號連接以避免觸發變更事件 self.language_selector.blockSignals(True) - + # 先清空現有選項 self.language_selector.clear() - + for i, (code, name) in enumerate(languages): self.language_selector.addItem(name, code) if code == current_language: self.language_selector.setCurrentIndex(i) - + # 重新連接信號 self.language_selector.blockSignals(False) - + def _on_language_changed(self, index: int) -> None: """語言變更事件處理""" if index < 0: return - + language_code = self.language_selector.itemData(index) if language_code and language_code != self.i18n.get_current_language(): # 先保存語言設定 @@ -444,14 +450,14 @@ class SettingsTab(QWidget): self.i18n.set_language(language_code) # 發出信號 self.language_changed.emit() - + def _on_layout_changed(self, button, checked: bool) -> None: """佈局變更事件處理""" if not checked: return - + button_id = self.layout_button_group.id(button) - + if button_id == 0: # 分離模式 new_combined_mode = False new_orientation = 'vertical' @@ -463,7 +469,7 @@ class SettingsTab(QWidget): new_orientation = 'horizontal' else: return - + # 檢查是否真的有變更 if new_combined_mode != self.combined_mode or new_orientation != self.layout_orientation: # 批量保存配置(避免多次寫入文件) @@ -471,20 +477,26 @@ class SettingsTab(QWidget): 'combined_mode': new_combined_mode, 'layout_orientation': new_orientation }) - + # 更新內部狀態 self.combined_mode = new_combined_mode self.layout_orientation = new_orientation - + # 發出佈局變更請求信號 self.layout_change_requested.emit(new_combined_mode, new_orientation) - + def _on_always_center_changed(self, checked: bool) -> None: """視窗定位選項變更事件處理""" # 立即保存設定 self.config_manager.set_always_center_window(checked) debug_log(f"視窗定位設置已保存: {checked}") # 調試輸出 + def _on_auto_focus_changed(self, checked: bool) -> None: + """自動聚焦選項變更事件處理""" + # 立即保存設定 + self.config_manager.set_auto_focus_enabled(checked) + debug_log(f"自動聚焦設置已保存: {checked}") # 調試輸出 + def _on_timeout_enabled_changed(self, enabled: bool) -> None: """超時啟用狀態變更事件處理""" # 立即保存設定 @@ -514,10 +526,10 @@ class SettingsTab(QWidget): QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) - + if reply == QMessageBox.Yes: self.reset_requested.emit() - + def update_texts(self) -> None: """更新界面文字(不重新創建界面)""" # 更新區塊標題 @@ -546,10 +558,12 @@ class SettingsTab(QWidget): # 更新按鈕文字 if hasattr(self, 'reset_button'): self.reset_button.setText(t('settings.reset.button')) - + # 更新切換開關文字 if hasattr(self, 'always_center_switch'): self.always_center_switch.setText(t('settings.window.alwaysCenter')) + if hasattr(self, 'auto_focus_switch'): + self.auto_focus_switch.setText(t('settings.window.autoFocus')) if hasattr(self, 'timeout_enabled_switch'): self.timeout_enabled_switch.setText(t('timeout.enable')) @@ -558,7 +572,7 @@ class SettingsTab(QWidget): self.ui_elements['timeout_duration_label'].setText(t('timeout.duration.label')) if hasattr(self, 'timeout_duration_spinbox'): self.timeout_duration_spinbox.setSuffix(" " + t('timeout.seconds')) - + # 更新單選按鈕文字 if hasattr(self, 'separate_mode_radio'): self.separate_mode_radio.setText(t('settings.layout.separateMode')) @@ -566,25 +580,29 @@ class SettingsTab(QWidget): self.combined_vertical_radio.setText(t('settings.layout.combinedVertical')) if hasattr(self, 'combined_horizontal_radio'): self.combined_horizontal_radio.setText(t('settings.layout.combinedHorizontal')) - + # 注意:不要重新填充語言選擇器,避免重複選項問題 - + def reload_settings_from_config(self) -> None: """從配置重新載入設定狀態""" # 重新載入語言設定 if hasattr(self, 'language_selector'): self._populate_language_selector() - + # 重新載入佈局設定 self.combined_mode = self.config_manager.get_layout_mode() self.layout_orientation = self.config_manager.get_layout_orientation() self._set_initial_layout_state() - + # 重新載入視窗設定 if hasattr(self, 'always_center_switch'): always_center = self.config_manager.get_always_center_window() self.always_center_switch.setChecked(always_center) debug_log(f"重新載入視窗定位設置: {always_center}") # 調試輸出 + if hasattr(self, 'auto_focus_switch'): + auto_focus = self.config_manager.get_auto_focus_enabled() + self.auto_focus_switch.setChecked(auto_focus) + debug_log(f"重新載入自動聚焦設置: {auto_focus}") # 調試輸出 # 重新載入超時設定 if hasattr(self, 'timeout_enabled_switch'): @@ -595,29 +613,29 @@ class SettingsTab(QWidget): timeout_duration = self.config_manager.get_timeout_duration() self.timeout_duration_spinbox.setValue(timeout_duration) debug_log(f"重新載入超時時間設置: {timeout_duration}") # 調試輸出 - + def set_layout_mode(self, combined_mode: bool) -> None: """設置佈局模式""" self.combined_mode = combined_mode self._set_initial_layout_state() - + def set_layout_orientation(self, orientation: str) -> None: """設置佈局方向""" self.layout_orientation = orientation self._set_initial_layout_state() - + def _set_initial_layout_state(self) -> None: """設置初始佈局狀態""" if hasattr(self, 'separate_mode_radio'): # 暫時斷開信號連接以避免觸發變更事件 self.layout_button_group.blockSignals(True) - + if not self.combined_mode: self.separate_mode_radio.setChecked(True) elif self.layout_orientation == 'vertical': self.combined_vertical_radio.setChecked(True) else: self.combined_horizontal_radio.setChecked(True) - + # 重新連接信號 - self.layout_button_group.blockSignals(False) \ No newline at end of file + self.layout_button_group.blockSignals(False) diff --git a/src/mcp_feedback_enhanced/gui/window/config_manager.py b/src/mcp_feedback_enhanced/gui/window/config_manager.py index ce73299..ccbc9b7 100644 --- a/src/mcp_feedback_enhanced/gui/window/config_manager.py +++ b/src/mcp_feedback_enhanced/gui/window/config_manager.py @@ -16,18 +16,18 @@ from ...debug import gui_debug_log as debug_log class ConfigManager: """配置管理器""" - + def __init__(self): self._config_file = self._get_config_file_path() self._config_cache = {} self._load_config() - + def _get_config_file_path(self) -> Path: """獲取配置文件路徑""" config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" config_dir.mkdir(parents=True, exist_ok=True) return config_dir / "ui_settings.json" - + def _load_config(self) -> None: """載入配置""" try: @@ -41,7 +41,7 @@ class ConfigManager: except Exception as e: debug_log(f"載入配置失敗: {e}") self._config_cache = {} - + def _save_config(self) -> None: """保存配置""" try: @@ -50,16 +50,16 @@ class ConfigManager: debug_log("配置文件保存成功") except Exception as e: debug_log(f"保存配置失敗: {e}") - + def get(self, key: str, default: Any = None) -> Any: """獲取配置值""" return self._config_cache.get(key, default) - + def set(self, key: str, value: Any) -> None: """設置配置值""" self._config_cache[key] = value self._save_config() - + def update_partial_config(self, updates: Dict[str, Any]) -> None: """批量更新配置項目,只保存指定的設定而不影響其他參數""" try: @@ -68,84 +68,84 @@ class ConfigManager: if self._config_file.exists(): with open(self._config_file, 'r', encoding='utf-8') as f: current_config = json.load(f) - + # 只更新指定的項目 for key, value in updates.items(): current_config[key] = value # 同時更新內存緩存 self._config_cache[key] = value - + # 保存到文件 with open(self._config_file, 'w', encoding='utf-8') as f: json.dump(current_config, f, ensure_ascii=False, indent=2) - + debug_log(f"部分配置已更新: {list(updates.keys())}") - + except Exception as e: debug_log(f"更新部分配置失敗: {e}") - + def get_layout_mode(self) -> bool: """獲取佈局模式(False=分離模式,True=合併模式)""" return self.get('combined_mode', False) - + def set_layout_mode(self, combined_mode: bool) -> None: """設置佈局模式""" self.update_partial_config({'combined_mode': combined_mode}) debug_log(f"佈局模式設置: {'合併模式' if combined_mode else '分離模式'}") - + def get_layout_orientation(self) -> str: """獲取佈局方向(vertical=垂直(上下),horizontal=水平(左右))""" return self.get('layout_orientation', 'vertical') - + def set_layout_orientation(self, orientation: str) -> None: """設置佈局方向""" if orientation not in ['vertical', 'horizontal']: orientation = 'vertical' self.update_partial_config({'layout_orientation': orientation}) debug_log(f"佈局方向設置: {'垂直(上下)' if orientation == 'vertical' else '水平(左右)'}") - + def get_language(self) -> str: """獲取語言設置""" return self.get('language', 'zh-TW') - + def set_language(self, language: str) -> None: """設置語言""" self.update_partial_config({'language': language}) debug_log(f"語言設置: {language}") - + def get_splitter_sizes(self, splitter_name: str) -> list: """獲取分割器尺寸""" sizes = self.get(f'splitter_sizes.{splitter_name}', []) if sizes: debug_log(f"載入分割器 {splitter_name} 尺寸: {sizes}") return sizes - + def set_splitter_sizes(self, splitter_name: str, sizes: list) -> None: """設置分割器尺寸""" self.update_partial_config({f'splitter_sizes.{splitter_name}': sizes}) debug_log(f"保存分割器 {splitter_name} 尺寸: {sizes}") - + def get_window_geometry(self) -> dict: """獲取窗口幾何信息""" geometry = self.get('window_geometry', {}) if geometry: debug_log(f"載入窗口幾何信息: {geometry}") return geometry - + def set_window_geometry(self, geometry: dict) -> None: """設置窗口幾何信息(使用部分更新避免覆蓋其他設定)""" self.update_partial_config({'window_geometry': geometry}) debug_log(f"保存窗口幾何信息: {geometry}") - + def get_always_center_window(self) -> bool: """獲取總是在主螢幕中心顯示視窗的設置""" return self.get('always_center_window', False) - + def set_always_center_window(self, always_center: bool) -> None: """設置總是在主螢幕中心顯示視窗""" self.update_partial_config({'always_center_window': always_center}) debug_log(f"視窗定位設置: {'總是中心顯示' if always_center else '智能定位'}") - + def get_image_size_limit(self) -> int: """獲取圖片大小限制(bytes),0 表示無限制""" return self.get('image_size_limit', 0) @@ -199,6 +199,15 @@ class ConfigManager: }) debug_log(f"超時設置: {'啟用' if enabled else '停用'}, {seconds} 秒") + def get_auto_focus_enabled(self) -> bool: + """獲取是否啟用自動聚焦到輸入框""" + return self.get('auto_focus_enabled', True) # 預設啟用 + + def set_auto_focus_enabled(self, enabled: bool) -> None: + """設置是否啟用自動聚焦到輸入框""" + self.update_partial_config({'auto_focus_enabled': enabled}) + debug_log(f"自動聚焦設置: {'啟用' if enabled else '停用'}") + def reset_settings(self) -> None: """重置所有設定到預設值""" try: @@ -214,4 +223,4 @@ class ConfigManager: except Exception as e: debug_log(f"重置設定失敗: {e}") - raise \ No newline at end of file + raise diff --git a/src/mcp_feedback_enhanced/gui/window/feedback_window.py b/src/mcp_feedback_enhanced/gui/window/feedback_window.py index 79e7cab..a46aa02 100644 --- a/src/mcp_feedback_enhanced/gui/window/feedback_window.py +++ b/src/mcp_feedback_enhanced/gui/window/feedback_window.py @@ -33,64 +33,73 @@ class FeedbackWindow(QMainWindow): self.result = None self.i18n = get_i18n_manager() self.mcp_timeout_seconds = timeout_seconds # MCP 傳入的超時時間 - + # 初始化組件 self.config_manager = ConfigManager() - + # 載入保存的語言設定 saved_language = self.config_manager.get_language() if saved_language: self.i18n.set_language(saved_language) - + self.combined_mode = self.config_manager.get_layout_mode() self.layout_orientation = self.config_manager.get_layout_orientation() - + # 設置窗口狀態保存的防抖計時器 self._save_timer = QTimer() self._save_timer.setSingleShot(True) self._save_timer.timeout.connect(self._delayed_save_window_position) self._save_delay = 500 # 500ms 延遲,避免過於頻繁的保存 - + # 設置UI self._setup_ui() self._setup_shortcuts() self._connect_signals() - + debug_log("主窗口初始化完成") # 如果啟用了超時,自動開始倒數計時 self.start_timeout_if_enabled() - + + # 設置定時器在窗口顯示後自動聚焦到輸入框(如果啟用) + if self.config_manager.get_auto_focus_enabled(): + self._focus_timer = QTimer() + self._focus_timer.setSingleShot(True) + self._focus_timer.timeout.connect(self._auto_focus_input) + self._focus_timer.start(300) # 延遲300ms確保窗口和UI元素完全加載 + else: + debug_log("自動聚焦已停用") + def _setup_ui(self) -> None: """設置用戶介面""" self.setWindowTitle(t('app.title')) self.setMinimumSize(400, 300) # 大幅降低最小窗口大小限制,允許用戶自由調整 self.resize(1200, 900) - + # 智能視窗定位 self._apply_window_positioning() - + # 中央元件 central_widget = QWidget() self.setCentralWidget(central_widget) - + # 主布局 main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(8) main_layout.setContentsMargins(16, 8, 16, 12) - + # 頂部專案目錄信息 self._create_project_header(main_layout) - + # 分頁區域 self._create_tab_area(main_layout) - + # 操作按鈕 self._create_action_buttons(main_layout) - + # 應用深色主題 self._apply_dark_style() - + def _create_project_header(self, layout: QVBoxLayout) -> None: """創建專案目錄頭部信息""" # 創建水平布局來放置專案目錄和倒數計時器 @@ -159,7 +168,7 @@ class FeedbackWindow(QMainWindow): self._update_countdown_visibility() - + def _create_tab_area(self, layout: QVBoxLayout) -> None: """創建分頁區域""" # 創建滾動區域來包裝整個分頁組件 @@ -220,21 +229,21 @@ class FeedbackWindow(QMainWindow): width: 0px; } """) - + self.tab_widget = QTabWidget() self.tab_widget.setMinimumHeight(150) # 降低分頁組件最小高度 # 設置分頁組件的大小策略,確保能觸發滾動 self.tab_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - + # 初始化分頁管理器 self.tab_manager = TabManager( - self.tab_widget, - self.project_dir, - self.summary, + self.tab_widget, + self.project_dir, + self.summary, self.combined_mode, self.layout_orientation ) - + # 創建分頁 self.tab_manager.create_tabs() @@ -243,21 +252,21 @@ class FeedbackWindow(QMainWindow): # 將分頁組件放入滾動區域 scroll_area.setWidget(self.tab_widget) - + layout.addWidget(scroll_area, 1) - + def _create_action_buttons(self, layout: QVBoxLayout) -> None: """創建操作按鈕""" button_layout = QHBoxLayout() button_layout.addStretch() - + # 取消按鈕 self.cancel_button = QPushButton(t('buttons.cancel')) self.cancel_button.clicked.connect(self._cancel_feedback) self.cancel_button.setFixedSize(130, 40) apply_widget_styles(self.cancel_button, "secondary_button") button_layout.addWidget(self.cancel_button) - + # 提交按鈕 self.submit_button = QPushButton(t('buttons.submit')) self.submit_button.clicked.connect(self._submit_feedback) @@ -265,19 +274,19 @@ class FeedbackWindow(QMainWindow): self.submit_button.setDefault(True) apply_widget_styles(self.submit_button, "success_button") button_layout.addWidget(self.submit_button) - + layout.addLayout(button_layout) - + def _setup_shortcuts(self) -> None: """設置快捷鍵""" # Ctrl+Enter (主鍵盤) 提交回饋 submit_shortcut_main = QShortcut(QKeySequence("Ctrl+Return"), self) submit_shortcut_main.activated.connect(self._submit_feedback) - + # Ctrl+Enter (數字鍵盤) 提交回饋 submit_shortcut_keypad = QShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Enter), self) submit_shortcut_keypad.activated.connect(self._submit_feedback) - + # macOS 支援 Cmd+Return (主鍵盤) submit_shortcut_mac_main = QShortcut(QKeySequence("Meta+Return"), self) submit_shortcut_mac_main.activated.connect(self._submit_feedback) @@ -285,19 +294,19 @@ class FeedbackWindow(QMainWindow): # macOS 支援 Cmd+Enter (數字鍵盤) submit_shortcut_mac_keypad = QShortcut(QKeySequence(Qt.Modifier.META | Qt.Key.Key_Enter), self) submit_shortcut_mac_keypad.activated.connect(self._submit_feedback) - + # Escape 取消回饋 cancel_shortcut = QShortcut(QKeySequence(Qt.Key_Escape), self) cancel_shortcut.activated.connect(self._cancel_feedback) - + def _connect_signals(self) -> None: """連接信號""" # 連接語言變更信號 self.language_changed.connect(self._refresh_ui_texts) - + # 連接分頁管理器的信號 self.tab_manager.connect_signals(self) - + def _apply_dark_style(self) -> None: """應用深色主題""" self.setStyleSheet(""" @@ -378,40 +387,40 @@ class FeedbackWindow(QMainWindow): border-color: #005a9e; } """) - + def _on_layout_change_requested(self, combined_mode: bool, orientation: str) -> None: """處理佈局變更請求(模式和方向同時變更)""" try: # 保存當前內容 current_data = self.tab_manager.get_feedback_data() - + # 記住當前分頁索引 current_tab_index = self.tab_widget.currentIndex() - + # 保存新設置 self.combined_mode = combined_mode self.layout_orientation = orientation self.config_manager.set_layout_mode(combined_mode) self.config_manager.set_layout_orientation(orientation) - + # 重新創建分頁 self.tab_manager.set_layout_mode(combined_mode) self.tab_manager.set_layout_orientation(orientation) self.tab_manager.create_tabs() - + # 恢復內容 self.tab_manager.restore_content( current_data["interactive_feedback"], current_data["command_logs"], current_data["images"] ) - + # 重新連接信號 self.tab_manager.connect_signals(self) - + # 刷新UI文字 self._refresh_ui_texts() - + # 恢復到設定頁面(通常是倒數第二個分頁) if self.combined_mode: # 合併模式:回饋、命令、設置、關於 @@ -419,107 +428,107 @@ class FeedbackWindow(QMainWindow): else: # 分離模式:回饋、摘要、命令、設置、關於 settings_tab_index = 3 - + # 確保索引在有效範圍內 if settings_tab_index < self.tab_widget.count(): self.tab_widget.setCurrentIndex(settings_tab_index) - + mode_text = "合併模式" if combined_mode else "分離模式" orientation_text = "(水平布局)" if orientation == "horizontal" else "(垂直布局)" if combined_mode: mode_text += orientation_text debug_log(f"佈局已切換到: {mode_text}") - + except Exception as e: debug_log(f"佈局變更失敗: {e}") QMessageBox.warning(self, t('errors.title'), t('errors.interfaceReloadError', error=str(e))) - - + + def _on_reset_settings_requested(self) -> None: """處理重置設定請求""" try: # 重置配置管理器的所有設定 self.config_manager.reset_settings() - + # 重置應用程式狀態 self.combined_mode = False # 重置為分離模式 self.layout_orientation = 'vertical' # 重置為垂直布局 - + # 重新設置語言為預設 self.i18n.set_language('zh-TW') - + # 保存當前內容 current_data = self.tab_manager.get_feedback_data() - + # 重新創建分頁 self.tab_manager.set_layout_mode(self.combined_mode) self.tab_manager.set_layout_orientation(self.layout_orientation) self.tab_manager.create_tabs() - + # 恢復內容 self.tab_manager.restore_content( current_data["interactive_feedback"], current_data["command_logs"], current_data["images"] ) - + # 重新連接信號 self.tab_manager.connect_signals(self) - + # 重新載入設定分頁的狀態 if self.tab_manager.settings_tab: self.tab_manager.settings_tab.reload_settings_from_config() - + # 刷新UI文字 self._refresh_ui_texts() - + # 重新應用視窗定位(使用重置後的設定) self._apply_window_positioning() - + # 切換到設定分頁顯示重置結果 settings_tab_index = 3 # 分離模式下設定分頁是第4個(索引3) if settings_tab_index < self.tab_widget.count(): self.tab_widget.setCurrentIndex(settings_tab_index) - + # 顯示成功訊息 QMessageBox.information( - self, + self, t('settings.reset.successTitle'), t('settings.reset.successMessage'), QMessageBox.Ok ) - + debug_log("設定重置成功") - + except Exception as e: debug_log(f"重置設定失敗: {e}") QMessageBox.critical( - self, + self, t('errors.title'), t('settings.reset.error', error=str(e)), QMessageBox.Ok ) - + def _submit_feedback(self) -> None: """提交回饋""" # 獲取所有回饋數據 data = self.tab_manager.get_feedback_data() - + self.result = data debug_log(f"回饋提交: 文字長度={len(data['interactive_feedback'])}, " f"命令日誌長度={len(data['command_logs'])}, " f"圖片數量={len(data['images'])}") - + # 關閉窗口 self.close() - + def _cancel_feedback(self) -> None: """取消回饋收集""" debug_log("取消回饋收集") self.result = "" self.close() - + def force_close(self) -> None: """強制關閉視窗(用於超時處理)""" debug_log("強制關閉視窗(超時)") @@ -644,7 +653,7 @@ class FeedbackWindow(QMainWindow): # 倒數計時器只在啟用超時時顯示 self.countdown_label.setVisible(self.timeout_enabled) self.countdown_display.setVisible(self.timeout_enabled) - + def _refresh_ui_texts(self) -> None: """刷新界面文字""" self.setWindowTitle(t('app.title')) @@ -660,11 +669,11 @@ class FeedbackWindow(QMainWindow): # 更新分頁文字 self.tab_manager.update_tab_texts() - + def _apply_window_positioning(self) -> None: """根據用戶設置應用視窗定位策略""" always_center = self.config_manager.get_always_center_window() - + if always_center: # 總是中心顯示模式:使用保存的大小(如果有的話),然後置中 self._restore_window_size_only() @@ -678,22 +687,22 @@ class FeedbackWindow(QMainWindow): else: # 沒有保存的位置,移到中心 self._move_to_primary_screen_center() - + def _is_window_visible(self) -> bool: """檢查視窗是否在任何螢幕的可見範圍內""" from PySide6.QtWidgets import QApplication - + window_rect = self.frameGeometry() - + for screen in QApplication.screens(): if screen.availableGeometry().intersects(window_rect): return True return False - + def _move_to_primary_screen_center(self) -> None: """將視窗移到主螢幕中心""" from PySide6.QtWidgets import QApplication - + screen = QApplication.primaryScreen() if screen: screen_geometry = screen.availableGeometry() @@ -702,7 +711,7 @@ class FeedbackWindow(QMainWindow): window_geometry.moveCenter(center_point) self.move(window_geometry.topLeft()) debug_log("視窗已移到主螢幕中心") - + def _restore_window_size_only(self) -> bool: """只恢復視窗大小(不恢復位置)""" try: @@ -714,7 +723,7 @@ class FeedbackWindow(QMainWindow): except Exception as e: debug_log(f"恢復視窗大小失敗: {e}") return False - + def _restore_last_position(self) -> bool: """嘗試恢復上次保存的視窗位置和大小""" try: @@ -727,18 +736,18 @@ class FeedbackWindow(QMainWindow): except Exception as e: debug_log(f"恢復視窗位置失敗: {e}") return False - + def _save_window_position(self) -> None: """保存當前視窗位置和大小""" try: always_center = self.config_manager.get_always_center_window() - + # 獲取當前幾何信息 current_geometry = { 'width': self.width(), 'height': self.height() } - + if not always_center: # 智能定位模式:同時保存位置 current_geometry['x'] = self.x() @@ -747,45 +756,86 @@ class FeedbackWindow(QMainWindow): else: # 總是中心顯示模式:只保存大小,不保存位置 debug_log(f"已保存視窗大小: {current_geometry['width']}x{current_geometry['height']} (總是中心顯示模式)") - + # 獲取現有配置,只更新需要的部分 saved_geometry = self.config_manager.get_window_geometry() or {} saved_geometry.update(current_geometry) - + self.config_manager.set_window_geometry(saved_geometry) - + except Exception as e: debug_log(f"保存視窗狀態失敗: {e}") - + def resizeEvent(self, event) -> None: """窗口大小變化事件""" super().resizeEvent(event) # 窗口大小變化時始終保存(無論是否設置為中心顯示) if hasattr(self, 'config_manager'): self._schedule_save_window_position() - + def moveEvent(self, event) -> None: """窗口位置變化事件""" super().moveEvent(event) # 窗口位置變化只在智能定位模式下保存 if hasattr(self, 'config_manager') and not self.config_manager.get_always_center_window(): self._schedule_save_window_position() - + def _schedule_save_window_position(self) -> None: """調度窗口位置保存(防抖機制)""" if hasattr(self, '_save_timer'): self._save_timer.start(self._save_delay) - + def _delayed_save_window_position(self) -> None: """延遲保存窗口位置(防抖機制的實際執行)""" self._save_window_position() - + + def _auto_focus_input(self) -> None: + """自動聚焦到輸入框""" + try: + # 確保窗口已經顯示並激活 + self.raise_() + self.activateWindow() + + # 獲取回饋輸入框(修正邏輯) + feedback_input = None + + # 檢查是否有tab_manager + if not hasattr(self, 'tab_manager'): + debug_log("tab_manager 不存在") + return + + # 檢查 feedback_tab(無論是合併模式還是分離模式) + if hasattr(self.tab_manager, 'feedback_tab') and self.tab_manager.feedback_tab: + if hasattr(self.tab_manager.feedback_tab, 'feedback_input'): + feedback_input = self.tab_manager.feedback_tab.feedback_input + debug_log("找到feedback_tab中的輸入框") + else: + debug_log("feedback_tab存在但沒有feedback_input屬性") + else: + debug_log("沒有找到feedback_tab") + + # 設置焦點到輸入框 + if feedback_input: + feedback_input.setFocus() + feedback_input.raise_() # 確保輸入框可見 + debug_log("已自動聚焦到輸入框") + else: + debug_log("未找到回饋輸入框,無法自動聚焦") + # 打印調試信息 + if hasattr(self, 'tab_manager'): + debug_log(f"tab_manager 屬性: {dir(self.tab_manager)}") + + except Exception as e: + debug_log(f"自動聚焦失敗: {e}") + import traceback + debug_log(f"詳細錯誤: {traceback.format_exc()}") + def closeEvent(self, event) -> None: """窗口關閉事件""" # 最終保存視窗狀態(大小始終保存,位置根據設置決定) self._save_window_position() - + # 清理分頁管理器 self.tab_manager.cleanup() event.accept() - debug_log("主窗口已關閉") \ No newline at end of file + debug_log("主窗口已關閉")