Merge branch 'feature/auto-focus-input-box' of github.com:penn201500/mcp-feedback-enhanced into penn201500-feature/auto-focus-input-box

This commit is contained in:
Minidoracat 2025-06-08 03:36:56 +08:00
commit 04798e4aa4
6 changed files with 272 additions and 192 deletions

View File

@ -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}"
}
}
}
}

View File

@ -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开源协作让技术变得更美好"
}
}
}

View File

@ -129,7 +129,8 @@
},
"window": {
"title": "視窗定位",
"alwaysCenter": "總是在主螢幕中心顯示視窗"
"alwaysCenter": "總是在主螢幕中心顯示視窗",
"autoFocus": "窗口打開時自動聚焦到輸入框"
},
"reset": {
"title": "重置設定",
@ -352,4 +353,4 @@
"findPortFailed": "❌ 尋找可用端口失敗: {error}"
}
}
}
}

View File

@ -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)
self.layout_button_group.blockSignals(False)

View File

@ -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:
"""獲取圖片大小限制bytes0 表示無限制"""
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
raise

View File

@ -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("主窗口已關閉")
debug_log("主窗口已關閉")