diff --git a/.gitignore b/.gitignore index 7a85b3c7f0677b9a48bb6ecb33a228a73f10a6c3..5692f319cd61623d0e822ef33da12da360ea8beb 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,6 @@ md转docx.py 文档/color_card_20260313_161405.json /测试 文档/代码审查报告-26.03.md +/创作过程 +bandit_report.html +bandit_project_report.html diff --git a/LICENSE b/LICENSE index 1a647a3363894becc3f746d49e448b99dc33e728..9a4d027b84f7a67ed279c37135c131f2c500d993 100644 --- a/LICENSE +++ b/LICENSE @@ -1028,7 +1028,20 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ -6. Open Color +6. PySideSix-Frameless-Window +-------------------------------------------------------------------------------- +版权所有:zhiyiYo +项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window +许可证:GNU Lesser General Public License v3.0 + +说明: +PySideSix-Frameless-Window 使用 LGPLv3 许可证。由于本项目主许可证为 GPLv3, +而 LGPLv3 是 GPLv3 的补充版本,完整的 LGPLv3 许可证文本请参考本文档 +"PySide6" 章节中的 "GNU LESSER GENERAL PUBLIC LICENSE Version 3" 部分。 + + +================================================================================ +7. Open Color -------------------------------------------------------------------------------- 版权所有:heeyeun (Yeun) 项目地址:https://github.com/yeun/open-color @@ -1060,7 +1073,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -7. Nice Color Palettes +8. Nice Color Palettes -------------------------------------------------------------------------------- 版权所有:Jam3 项目地址:https://github.com/Experience-Monks/nice-color-palettes @@ -1090,7 +1103,7 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -8. Tailwind CSS Colors +9. Tailwind CSS Colors -------------------------------------------------------------------------------- 版权所有:Tailwind Labs, Inc. 项目地址:https://github.com/tailwindlabs/tailwindcss @@ -1122,7 +1135,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -9. Material Design Colors (Apache License 2.0) +10. Material Design Colors (Apache License 2.0) -------------------------------------------------------------------------------- 版权所有:Google LLC 项目地址:https://m3.material.io/styles/color/system/overview @@ -1134,7 +1147,7 @@ Material Design Colors 使用 Apache License 2.0 许可证。完整的许可证 requests 章节中的 "Apache License Version 2.0" 部分。 ================================================================================ -10. ColorBrewer (Apache License 2.0) +11. ColorBrewer (Apache License 2.0) -------------------------------------------------------------------------------- 版权所有:Cynthia Brewer 官网:https://colorbrewer2.org/ @@ -1146,7 +1159,7 @@ ColorBrewer 使用 Apache License 2.0 许可证。完整的许可证文本请参 requests 章节中的 "Apache License Version 2.0" 部分。 ================================================================================ -11. Radix UI Colors +12. Radix UI Colors -------------------------------------------------------------------------------- 版权所有:WorkOS 项目地址:https://github.com/radix-ui/colors @@ -1179,7 +1192,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -12. Nord +13. Nord -------------------------------------------------------------------------------- 版权所有:Sven Greb 项目地址:https://github.com/arcticicestudio/nord @@ -1211,7 +1224,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -13. Dracula +14. Dracula --------------------------------------------------------------------------------- 版权所有:Dracula Theme contributors 官网:https://draculatheme.com/ @@ -1242,7 +1255,7 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -14. Rosé Pine +15. Rosé Pine --------------------------------------------------------------------------------- 版权所有:Rosé Pine 团队 项目地址:https://github.com/rose-pine/rose-pine-theme @@ -1274,7 +1287,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -15. Solarized +16. Solarized -------------------------------------------------------------------------------- 版权所有:Ethan Schoonover 项目地址:https://github.com/altercation/solarized @@ -1304,7 +1317,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -16. Catppuccin +17. Catppuccin -------------------------------------------------------------------------------- 版权所有:Catppuccin 团队 项目地址:https://github.com/catppuccin/catppuccin @@ -1337,7 +1350,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -17. Gruvbox +18. Gruvbox -------------------------------------------------------------------------------- 版权所有:Pavel Pertsev 项目地址:https://github.com/morhetz/gruvbox @@ -1367,7 +1380,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -18. Tokyo Night +19. Tokyo Night -------------------------------------------------------------------------------- 版权所有:enkia 项目地址:https://github.com/enkia/tokyo-night-vscode-theme @@ -1400,7 +1413,7 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -19. 网站使用资源 +20. 网站使用资源 -------------------------------------------------------------------------------- 项目官网 (https://qingshangongzai.github.io/Color_Card/) 使用了以下资源: diff --git a/README.md b/README.md index 00ba75511fe2b55d7f0781261bf8f04ed695c88f..c2c02df644bf694e6c1629091d1b1e52b5c9f1fd 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ 简体中文 | English
- ---- +*** @@ -29,50 +28,51 @@ **开源地址**: -- **主仓库(Gitee)**:https://gitee.com/qingshangongzai/color_card -- **镜像仓库(GitHub)**:https://github.com/qingshangongzai/Color_Card -- **官方网站**:https://qingshangongzai.github.io/Color_Card/ +- **主仓库(Gitee)**:版权所有:zhiyiYo
+项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window
+许可证:GNU Lesser General Public License v3.0
+PySideSix-Frameless-Window 使用 LGPLv3 许可证。由于本项目主许可证为 GPLv3,而 LGPLv3 是 GPLv3 的补充版本,完整的 LGPLv3 许可证文本请参考本文档 PySide6 章节中的 "GNU LESSER GENERAL PUBLIC LICENSE Version 3" 部分。
+版权所有:heeyeun (Yeun)
项目地址:https://github.com/yeun/open-color
@@ -714,7 +729,7 @@版权所有:Jam3
项目地址:https://github.com/Experience-Monks/nice-color-palettes
@@ -748,7 +763,7 @@版权所有:Tailwind Labs, Inc.
项目地址:https://github.com/tailwindlabs/tailwindcss
@@ -771,7 +786,7 @@版权所有:Google LLC
项目地址:https://m3.material.io/styles/color/system/overview
@@ -787,7 +802,7 @@版权所有:Cynthia Brewer
项目地址:https://colorbrewer2.org/
@@ -804,7 +819,7 @@版权所有:WorkOS
项目地址:https://github.com/radix-ui/colors
@@ -828,7 +843,7 @@版权所有:Sven Greb
项目地址:https://github.com/arcticicestudio/nord
@@ -851,7 +866,7 @@版权所有:Dracula Theme contributors
@@ -873,7 +888,7 @@版权所有:Rosé Pine 团队
项目地址:https://github.com/rose-pine/rose-pine-theme
@@ -896,7 +911,7 @@版权所有:Ethan Schoonover
项目地址:https://github.com/altercation/solarized
@@ -916,7 +931,7 @@版权所有:Catppuccin 团队
项目地址:https://github.com/catppuccin/catppuccin
@@ -939,7 +954,7 @@版权所有:Pavel Pertsev
项目地址:https://github.com/morhetz/gruvbox
@@ -961,7 +976,7 @@版权所有:enkia
项目地址:https://github.com/enkia/tokyo-night-vscode-theme
diff --git a/locales/FR_FR.json b/locales/FR_FR.json index 9d0ac79b984504b0d4983ed6e1ca6a9d0beae8a3..df106d3c2f03468f8c218f1098a7bd48929b56c3 100644 --- a/locales/FR_FR.json +++ b/locales/FR_FR.json @@ -229,6 +229,7 @@ "language": "Paramètres de langue", "language_title": "Langue de l'interface", "language_desc": "Sélectionner la langue d'affichage de l'application", + "language_auto": "Suivre le système", "help": "Aide", "check_update": "Vérifier", "version_update": "Mise à jour", diff --git a/locales/JA_JP.json b/locales/JA_JP.json index b20d7b62ac7749af5c4e4fea0449694a743099ff..621bfe544ba2774b78e33b9a47148efe0a7a5b87 100644 --- a/locales/JA_JP.json +++ b/locales/JA_JP.json @@ -229,6 +229,7 @@ "language": "言語設定", "language_title": "インターフェース言語", "language_desc": "アプリケーションの表示言語を選択", + "language_auto": "システムに従う", "help": "ヘルプ", "check_update": "更新を確認", "version_update": "バージョン更新", diff --git a/locales/RU_RU.json b/locales/RU_RU.json index 58af60fc789445cfeea4754adf1b07e3eee2fb41..10e9633f1a913b2934d42776b343b4834345002c 100644 --- a/locales/RU_RU.json +++ b/locales/RU_RU.json @@ -229,6 +229,7 @@ "language": "Настройки языка", "language_title": "Язык интерфейса", "language_desc": "Выберите язык отображения приложения", + "language_auto": "Следовать системе", "help": "Справка", "check_update": "Проверить", "version_update": "Обновление версии", diff --git a/locales/ZW_FT.json b/locales/ZW_FT.json index 53d0cff2feadc4381c2304446d04930388e49a87..a8a86c356c1f59d77c1b55d5823f7f2f3e5dd1fa 100644 --- a/locales/ZW_FT.json +++ b/locales/ZW_FT.json @@ -229,6 +229,7 @@ "language": "語言設置", "language_title": "頁面語言", "language_desc": "選擇應用程序的顯示語言", + "language_auto": "跟隨系統", "help": "幫助", "check_update": "檢查更新", "version_update": "版本更新", diff --git a/locales/ZW_JT.json b/locales/ZW_JT.json index 16672d745048efd60dbb53c0ac4a5a0f0e346718..fa123ea19aefdbb61502f1ffa37110fe249315a4 100644 --- a/locales/ZW_JT.json +++ b/locales/ZW_JT.json @@ -229,6 +229,7 @@ "language": "语言设置", "language_title": "页面语言", "language_desc": "选择应用程序的显示语言", + "language_auto": "跟随系统", "help": "帮助", "check_update": "检查更新", "version_update": "版本更新", diff --git a/locales/en_US.json b/locales/en_US.json index 1ecb3cb99449fa785622c7295bce9bdef127eede..572ddf39d5dccc8e63a1b9cccb1ac59b588edec6 100644 --- a/locales/en_US.json +++ b/locales/en_US.json @@ -229,6 +229,7 @@ "language": "Language Settings", "language_title": "Interface Language", "language_desc": "Select the display language for the application", + "language_auto": "Follow System", "help": "Help", "check_update": "Check for Updates", "version_update": "Version Update", diff --git a/main.py b/main.py index d3e365042cc857c0a3bd5243762a54f202b2a29c..1ab97b94224f71152a5c31f4f1feba47f46026a7 100644 --- a/main.py +++ b/main.py @@ -16,7 +16,8 @@ def set_app_user_model_id(): app_id = 'HXiaoStudio.ColorCard.1.0.0' ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id) return True - except Exception: + except Exception as e: + logger.debug(f"设置 AppUserModelID 失败: {e}") return False @@ -96,7 +97,8 @@ def _create_splash_screen(): splash.setWindowFlags( Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint | - Qt.WindowType.SplashScreen + Qt.WindowType.SplashScreen | + Qt.WindowType.WindowDoesNotAcceptFocus ) # 居中显示 @@ -118,7 +120,8 @@ def _create_splash_screen(): ) return splash - except Exception: + except Exception as e: + logger.debug(f"创建启动画面失败: {e}") return None @@ -166,7 +169,7 @@ def main(): sys.stdout = _old_stdout # 安装自定义 Qt 消息处理器以过滤 QFont 警告 - def qt_message_handler(mode, context, message): + def qt_message_handler(mode, _context, message): """自定义 Qt 消息处理器,过滤掉 QFont::setPointSize 警告""" if "QFont::setPointSize: Point size <= 0" in message: return @@ -180,7 +183,7 @@ def main(): from core import get_config_manager logger.info("core 模块导入完成") - from utils import fix_windows_taskbar_icon_for_window, load_icon_universal, tr, get_locale_manager + from utils import fix_windows_taskbar_icon_for_window, load_icon_universal, get_locale_manager logger.info("utils 模块导入完成") from ui import MainWindow @@ -226,6 +229,9 @@ def main(): if splash: splash.finish(window) fix_windows_taskbar_icon_for_window(window) + # 强制激活主窗口,确保在其他窗口操作后仍能弹出 + window.activateWindow() + window.raise_() QTimer.singleShot(100, _on_window_shown) diff --git a/requirements.txt b/requirements.txt index dd0acbc8097bc6bcf3a1bbac53472e57c972cc28..2863ba367b14100188f5fa56cd77683d87b34857 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ PySide6>=6.0.0 PySide6-Fluent-Widgets>=1.0.0 +PySideSix-Frameless-Window>=0.1.0 Pillow>=9.0.0 requests>=2.32.0 numpy>=1.21.0 diff --git a/tests/test_logger.py b/tests/test_logger.py index 8b360664ba391f7a68946b4968966c65af1e4ebf..a0da9c7190f320ee178cf9926753da5a8081c120 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -7,7 +7,6 @@ import logging import time from pathlib import Path -from typing import Any, Dict from unittest.mock import patch # 第三方库导入 diff --git a/tests/test_threading.py b/tests/test_threading.py index 8a23c55b1c29bf65c321aa448a376811aa895936..1acf107fed36c03b1cb7da1775e3daaa8fec165c 100644 --- a/tests/test_threading.py +++ b/tests/test_threading.py @@ -5,19 +5,18 @@ # 标准库导入 import time -from typing import List, Tuple # 第三方库导入 import pytest -from PySide6.QtCore import Qt, QCoreApplication, QThread +from PySide6.QtCore import Qt from PySide6.QtGui import QImage # 项目模块导入 from core.histogram_service import HistogramCalculator, HistogramService from core.luminance_service import LuminanceCalculator, LuminanceService from core.color_service import DominantColorExtractor, ColorService -from core.image_service import ProgressiveImageLoader, ImageService -from core.palette_service import PaletteImporter, PaletteExporter, PaletteService +from core.image_service import ProgressiveImageLoader +from core.palette_service import PaletteImporter, PaletteExporter class TestHistogramCalculator: diff --git a/ui/canvases.py b/ui/canvases.py index 01522960f01c1ca0dea6b9f04e022588cb8061ff..6eae108f14e7c2340f81ef62c249b0a08a6cc9bc 100644 --- a/ui/canvases.py +++ b/ui/canvases.py @@ -10,7 +10,7 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel from qfluentwidgets import Action, FluentIcon, RoundMenu # 项目模块导入 -from core import get_luminance, get_zone, ServiceFactory, ZONE_WIDTH, log_user_action +from core import get_luminance, get_zone, ServiceFactory, log_user_action from utils import tr from .color_picker import ColorPicker from .zoom_viewer import ZoomViewer @@ -162,11 +162,19 @@ class BaseCanvas(QWidget): self.update() def resizeEvent(self, event) -> None: - """窗口大小改变时更新加载状态组件位置""" + """窗口大小改变时处理相关逻辑""" super().resizeEvent(event) + + # 更新加载状态组件位置 if self._is_loading: self._loading_widget.setGeometry(self.rect()) + # 重新调整图片 + if self._image and not self._image.isNull(): + self.update_picker_positions() + self.extract_all() + self.update() + def set_image(self, image_path: str) -> None: """异步加载并显示图片(使用ImageService分阶段加载,非阻塞) @@ -638,15 +646,6 @@ class BaseCanvas(QWidget): self.open_image_requested.emit() event.accept() - def resizeEvent(self, event) -> None: - """窗口大小改变时重新调整图片""" - super().resizeEvent(event) - if self._image and not self._image.isNull(): - # 窗口大小改变时,更新取色点位置并重新提取数据 - self.update_picker_positions() - self.extract_all() - self.update() - def contextMenuEvent(self, event) -> None: """右键菜单事件""" # 只有在有图片时才显示右键菜单 diff --git a/ui/cards.py b/ui/cards.py index 8597508aead142cd0bfbde37960cfe436adfce71..3871c8f596d6d4ed7793ca87bbbd040015238655 100644 --- a/ui/cards.py +++ b/ui/cards.py @@ -80,9 +80,7 @@ class BaseCardPanel(QWidget): old_count = self._card_count self._card_count = count - - layout = self.layout() - + if count > old_count: self._add_cards(old_count, count) else: @@ -285,9 +283,14 @@ class ColorCard(BaseCard): super().__init__(index, parent) # 监听主题变化 self._theme_connection = qconfig.themeChangedFinished.connect( - self._update_color_block_style + self._update_styles ) + def _update_styles(self): + """更新样式以适配主题""" + self._update_hex_button_style() + self._update_color_block_style() + def closeEvent(self, event): """关闭事件 - 断开信号连接""" try: diff --git a/ui/color_extract.py b/ui/color_extract.py index ce8f031a90b06329408f5b5912794e6ffd4d4347..1c0fc181c478c61af41b32568daf6048787f8a1c 100644 --- a/ui/color_extract.py +++ b/ui/color_extract.py @@ -7,9 +7,10 @@ # 标准库导入 import uuid from datetime import datetime +from pathlib import Path # 第三方库导入 -from PySide6.QtCore import Qt, Signal +from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QFileDialog, QHBoxLayout, QSplitter, QStackedWidget, QSizePolicy, QVBoxLayout, QWidget @@ -21,7 +22,7 @@ from qfluentwidgets import ( # 项目模块导入 from core import get_color_info, get_config_manager, ServiceFactory, log_user_action -from utils import tr, get_locale_manager +from utils import tr, get_locale_manager, get_default_image_directory, get_last_directory, set_last_directory from dialogs import EditPaletteDialog from .canvases import ImageCanvas from .cards import ColorCardPanel @@ -194,7 +195,7 @@ class ColorExtractInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_extract.select_image'), - "", + get_last_directory("image_import", get_default_image_directory()), tr('color_extract.image_filter') ) @@ -204,10 +205,12 @@ class ColorExtractInterface(QWidget): params={"path": file_path, "source": "color_extract"}, result="success" ) + set_last_directory("image_import", str(Path(file_path).parent)) self.image_canvas.set_image(file_path) def on_image_loaded(self, file_path): - """图片加载完成回调""" + """图片加载完成回调(由主窗口同步时调用)""" + # 图片数据处理已在 on_image_data_loaded 中完成 pass def on_image_data_loaded(self, pixmap, image): diff --git a/ui/color_generation.py b/ui/color_generation.py index 2f0ecf3da6517974d13b2078a73f7f3a2c973a00..f41d719e7564d107330ffa2b0c67a626911b39ba 100644 --- a/ui/color_generation.py +++ b/ui/color_generation.py @@ -15,14 +15,15 @@ from PySide6.QtWidgets import ( QSplitter ) from PySide6.QtCore import Qt, Signal, QTimer -from PySide6.QtGui import QColor from qfluentwidgets import ( - CardWidget, PushButton, ToolButton, FluentIcon, InfoBar, InfoBarPosition, + PushButton, ToolButton, FluentIcon, InfoBar, InfoBarPosition, qconfig, isDarkTheme, ComboBox, PrimaryPushButton, Slider ) # 项目模块导入 -from core import get_color_info, get_config_manager, hsb_to_rgb, rgb_to_hsb, adjust_brightness +from core import ( + get_color_info, get_config_manager, hsb_to_rgb, rgb_to_hsb, rgb_hue_to_ryb_hue, ryb_hue_to_rgb_hue +) from utils import tr, get_locale_manager from dialogs import EditPaletteDialog from .cards import BaseCard, BaseCardPanel, ColorModeContainer, get_text_color, get_placeholder_color, get_border_color @@ -47,7 +48,12 @@ class GenerationColorInfoCard(BaseCard): self._hex_visible = True super().__init__(index, parent) # 监听主题变化 - qconfig.themeChangedFinished.connect(self._update_color_block_style) + qconfig.themeChangedFinished.connect(self._update_styles) + + def _update_styles(self): + """更新样式以适配主题""" + self._update_hex_button_style() + self._update_color_block_style() def setup_ui(self): """设置界面""" @@ -482,8 +488,7 @@ class ColorGenerationInterface(QWidget): self.random_btn.setText(tr('color_generation.random')) self.favorite_button.setText(tr('color_generation.favorite')) self.brightness_label.setText(tr('color_generation.brightness')) - - current_index = self.scheme_combo.currentIndex() + self.scheme_combo.setItemText(0, tr('color_generation.schemes.monochromatic')) self.scheme_combo.setItemText(1, tr('color_generation.schemes.analogous')) self.scheme_combo.setItemText(2, tr('color_generation.schemes.complementary')) @@ -550,7 +555,7 @@ class ColorGenerationInterface(QWidget): def on_base_color_changed(self, h, s, b): """基准颜色改变回调 - 色相变化时,所有采样点跟随旋转; + 色相变化时,所有采样点跟随旋转,保持相对角度关系; 饱和度变化时,仅基准点变化,其他采样点保持原位。 """ # 计算色相变化量 @@ -562,17 +567,28 @@ class ColorGenerationInterface(QWidget): self._base_hue = h self._base_saturation = s - # RYB模式下,重新生成配色以保持RYB色轮上的相对角度关系 - if self._color_wheel_mode == 'RYB' and delta_h != 0: - self._generate_scheme_colors() - return - - # 色相变化:所有采样点跟着旋转(仅RGB模式) + # 色相变化:所有采样点跟着旋转 if delta_h != 0 and self._scheme_colors: - for i in range(len(self._scheme_colors)): - old_h, old_s, old_b = self._scheme_colors[i] - new_h = (old_h + delta_h) % 360 - self._scheme_colors[i] = (new_h, old_s, old_b) + if self._color_wheel_mode == 'RYB': + # RYB模式下需要在RYB色轮上进行偏移,保持RYB角度关系 + # 将RGB的delta_h转换为RYB的delta_h + old_base_ryb = rgb_hue_to_ryb_hue(self._base_hue - delta_h) + new_base_ryb = rgb_hue_to_ryb_hue(self._base_hue) + ryb_delta_h = new_base_ryb - old_base_ryb + + for i in range(len(self._scheme_colors)): + old_h, old_s, old_b = self._scheme_colors[i] + # RGB -> RYB -> 偏移 -> RGB + ryb_h = rgb_hue_to_ryb_hue(old_h) + new_ryb_h = (ryb_h + ryb_delta_h) % 360 + new_h = ryb_hue_to_rgb_hue(new_ryb_h) + self._scheme_colors[i] = (new_h, old_s, old_b) + else: + # RGB模式下直接在RGB色轮上偏移 + for i in range(len(self._scheme_colors)): + old_h, old_s, old_b = self._scheme_colors[i] + new_h = (old_h + delta_h) % 360 + self._scheme_colors[i] = (new_h, old_s, old_b) # 饱和度变化:更新 _scheme_colors[0](基准点) if delta_s != 0 and self._scheme_colors: @@ -601,7 +617,6 @@ class ColorGenerationInterface(QWidget): self._scheme_colors[index] = (h, s, b) # 转换为RGB并更新色块面板 - rgb = hsb_to_rgb(h, s, b) self.color_panel.set_colors([hsb_to_rgb(*c) for c in self._scheme_colors]) def on_brightness_changed(self, value): diff --git a/ui/color_preview.py b/ui/color_preview.py index b0b5b3709f407996dc411081390182ddc90487bc..a34c1821e332958b7ad4adceee18c6c542d3d297 100644 --- a/ui/color_preview.py +++ b/ui/color_preview.py @@ -12,6 +12,7 @@ - 配色预览界面 """ # 标准库导入 +from pathlib import Path from typing import List, Optional, Dict, Any, Type # 第三方库导入 @@ -35,7 +36,7 @@ from core.color import get_color_info from core.logger import get_logger, log_user_action from dialogs.edit_palette import EditPaletteDialog from dialogs.export_settings_dialog import ExportSettingsDialog -from utils import tr, get_locale_manager +from utils import tr, get_locale_manager, get_default_image_directory, get_last_directory, set_last_directory from utils.theme_colors import get_border_color, get_text_color logger = get_logger("color_preview") @@ -306,7 +307,7 @@ class ColorDotBar(QWidget): } # 打开编辑对话框(预览配色场景不显示名称输入) - dialog = EditPaletteDialog(palette_data=palette_data, parent=self, show_name_input=False) + dialog = EditPaletteDialog(palette_data=palette_data, parent=self.window(), show_name_input=False) if dialog.exec() == EditPaletteDialog.DialogCode.Accepted: new_palette_data = dialog.get_palette_data() if new_palette_data and 'colors' in new_palette_data: @@ -1882,13 +1883,15 @@ class ColorPreviewInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_preview.import_svg'), - "", + get_last_directory("svg_import", get_default_image_directory()), tr('color_preview.svg_filter') ) if not file_path: return + set_last_directory("svg_import", str(Path(file_path).parent)) + svg_preview = self.preview_panel.get_svg_preview() if svg_preview is None: InfoBar.warning( @@ -1960,12 +1963,13 @@ class ColorPreviewInterface(QWidget): return # 打开文件保存对话框(获取保存目录) + last_dir = get_last_directory("svg_export", get_default_image_directory()) if len(selected_indices) == 1: # 单张图片 - default_name = f"{filename_prefix}.{export_format}" + default_name = str(Path(last_dir) / f"{filename_prefix}.{export_format}") else: # 多张图片 - default_name = filename_prefix + default_name = str(Path(last_dir) / filename_prefix) file_path, _ = QFileDialog.getSaveFileName( self, @@ -1977,6 +1981,8 @@ class ColorPreviewInterface(QWidget): if not file_path: return + set_last_directory("svg_export", str(Path(file_path).parent)) + # 执行导出 success_count = 0 failed_messages = [] @@ -2007,7 +2013,6 @@ class ColorPreviewInterface(QWidget): base_path = base_path[:-len(ext)] break # 获取保存目录 - from pathlib import Path path_obj = Path(file_path) parent_dir = path_obj.parent # 生成带序号的文件名 @@ -2081,13 +2086,15 @@ class ColorPreviewInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_preview.import_template'), - "", + get_last_directory("svg_import", get_default_image_directory()), tr('color_preview.svg_filter') ) if not file_path: return + set_last_directory("svg_import", str(Path(file_path).parent)) + is_valid, error_msg = self._get_preview_service().validate_svg_file(file_path) if not is_valid: InfoBar.error( diff --git a/ui/color_wheel.py b/ui/color_wheel.py index 26fc12129d0959b5a8c277e7a64c65babbcf3d09..766b0f1309ac7b2a181b317a96aa0293b7ab2cb9 100644 --- a/ui/color_wheel.py +++ b/ui/color_wheel.py @@ -127,9 +127,10 @@ class HSBColorWheel(QWidget): """ import math - # 色相转换为角度(0°在上方12点钟方向,逆时针增加) + # 色相转换为角度(0°在上方12点钟方向,顺时针增加) # 加上90度将0°从右侧(3点钟)旋转到上方(12点钟) - angle_rad = ((h + 90) * math.pi / 180.0) + # 360-h实现水平翻转,使色相顺时针排列 + angle_rad = ((360 - h + 90) * math.pi / 180.0) # 饱和度转换为半径(0%在中心,100%在边缘) # 使用完整的色轮半径,让采样点可以到达圆周 @@ -175,6 +176,7 @@ class HSBColorWheel(QWidget): # 减90度偏移,使0°色相(红色)位于12点钟方向 angle = math.atan2(-dy, dx) - math.pi / 2 hue = (angle / (2 * math.pi)) % 1.0 + hue = (1.0 - hue) % 1.0 # 水平翻转:色相取反,实现顺时针排列 # 计算饱和度(距离中心的远近) saturation = min(distance / self._wheel_radius, 1.0) @@ -306,7 +308,8 @@ class HSBColorWheel(QWidget): for angle, label in hue_labels: # 计算标签位置(注意Y轴翻转) # 加上90度将0°从右侧(3点钟)旋转到上方(12点钟) - adjusted_angle = angle + 90 + # 360-angle实现水平翻转,使标签顺时针排列 + adjusted_angle = (360 - angle) + 90 rad = math.radians(adjusted_angle) x = self._center_x + label_radius * math.cos(rad) y = self._center_y - label_radius * math.sin(rad) @@ -457,7 +460,8 @@ class InteractiveColorWheel(QWidget): (x, y) 坐标 """ # 加上90度将0°从右侧(3点钟)旋转到上方(12点钟) - angle_rad = ((h + 90) * math.pi / 180.0) + # 360-h实现水平翻转,使色相顺时针排列 + angle_rad = ((360 - h + 90) * math.pi / 180.0) # 使用完整的色轮半径,让采样点可以到达圆周 max_radius = self._wheel_radius @@ -491,6 +495,7 @@ class InteractiveColorWheel(QWidget): angle = math.atan2(-dy, dx) # 减90度偏移,使0°色相(红色)位于12点钟方向 hue = ((angle - math.pi / 2) / (2 * math.pi)) % 1.0 * 360 + hue = (360 - hue) % 360 # 水平翻转:色相取反,实现顺时针排列 return hue, saturation @@ -596,6 +601,7 @@ class InteractiveColorWheel(QWidget): angle = math.atan2(-dy, dx) # 减90度偏移,使0°色相(红色)位于12点钟方向 hue = ((angle - math.pi / 2) / (2 * math.pi)) % 1.0 + hue = (1.0 - hue) % 1.0 # 水平翻转:色相取反,实现顺时针排列 saturation = min(distance / self._wheel_radius, 1.0) # 使用全局明度值 value = brightness_value @@ -675,7 +681,8 @@ class InteractiveColorWheel(QWidget): for angle, label in hue_labels: # 计算标签位置(注意Y轴翻转) # 加上90度将0°从右侧(3点钟)旋转到上方(12点钟) - adjusted_angle = angle + 90 + # 360-angle实现水平翻转,使标签顺时针排列 + adjusted_angle = (360 - angle) + 90 rad = math.radians(adjusted_angle) x = self._center_x + label_radius * math.cos(rad) y = self._center_y - label_radius * math.sin(rad) diff --git a/ui/gradient_extract.py b/ui/gradient_extract.py index 271da410c4192e4c51c8582b3e47c8fdcc43be01..b5b4ddc86c01a099e5dc54a2c17e9cb6216641f3 100644 --- a/ui/gradient_extract.py +++ b/ui/gradient_extract.py @@ -7,20 +7,19 @@ from typing import List, Tuple from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QColor, QPainter from PySide6.QtWidgets import ( - QApplication, QDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, - QSizePolicy, QSplitter, QVBoxLayout, QWidget + QDialog, QHBoxLayout, QLabel, QLineEdit, QSizePolicy, QSplitter, QVBoxLayout, QWidget ) from qfluentwidgets import ( - FluentIcon, InfoBar, InfoBarPosition, PushButton, Slider, ToolButton, qconfig, isDarkTheme, ScrollArea + FluentIcon, InfoBar, InfoBarPosition, PushButton, Slider, qconfig, ScrollArea ) # 项目模块导入 -from core import generate_gradient, generate_random_gradient, get_color_info, rgb_to_hex +from core import generate_gradient, generate_random_gradient, get_color_info from core import get_config_manager from core.logger import get_logger, log_user_action from ui.cards import ColorCard from utils import tr, get_locale_manager, calculate_grid_columns -from utils.theme_colors import get_border_color, get_card_background_color, get_text_color +from utils.theme_colors import get_border_color, get_text_color logger = get_logger("gradient_extract") @@ -425,7 +424,6 @@ class GradientExtractInterface(QWidget): def _update_hex_input_style(self): """更新16进制输入框样式(与配色管理一致)""" primary_color = get_text_color(secondary=False) - secondary_color = get_text_color(secondary=True) border_color = get_border_color() input_style = f""" @@ -584,7 +582,8 @@ class GradientExtractInterface(QWidget): # 将HEX转换为RGB r, g, b = self._hex_to_rgb(current_color) - dialog = ColorPickerDialog((r, g, b), self) + # 使用顶层窗口作为父窗口,避免背景色异常和两套窗口控制器 + dialog = ColorPickerDialog((r, g, b), self.window()) if dialog.exec() == QDialog.DialogCode.Accepted: color_info = dialog.get_color_info() if color_info: diff --git a/ui/histograms.py b/ui/histograms.py index d703329b79985d27265d3d1b992ea7e9fba03e8d..62507bcd2751f5b3ecb927040a88470b73cb3838 100644 --- a/ui/histograms.py +++ b/ui/histograms.py @@ -1,6 +1,6 @@ # 第三方库导入 import math -from typing import List, Optional +from typing import List from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QColor, QFont, QLinearGradient, QPainter, QPen, QMouseEvent from PySide6.QtWidgets import QWidget diff --git a/ui/luminance_extract.py b/ui/luminance_extract.py index 9abaea6f439626cea1eca7de857a7b82fef22060..1b737a0d4e9528b9f23f4be02f7fdac3a0017fe4 100644 --- a/ui/luminance_extract.py +++ b/ui/luminance_extract.py @@ -4,6 +4,7 @@ """ # 标准库导入 +from pathlib import Path from typing import Dict, Any from PySide6.QtCore import Qt, QTimer, Signal @@ -12,7 +13,7 @@ from PySide6.QtWidgets import QFileDialog, QSplitter, QVBoxLayout, QWidget # 项目模块导入 from core import LuminanceService from core.logger import get_logger, log_user_action -from utils import tr, get_locale_manager +from utils import tr, get_locale_manager, get_default_image_directory, get_last_directory, set_last_directory from .canvases import LuminanceCanvas from .histograms import LuminanceHistogramWidget @@ -100,12 +101,13 @@ class LuminanceExtractInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('luminance_extract.select_image'), - "", + get_last_directory("image_import", get_default_image_directory()), tr('luminance_extract.image_filter') ) if file_path: log_user_action("open_image", {"file_path": file_path}) + set_last_directory("image_import", str(Path(file_path).parent)) self._load_image(file_path) def change_image(self): diff --git a/ui/main_window.py b/ui/main_window.py index 6a55395841d3a42dfea7cf7fcd6c999a54475ec5..a312b156726556d25625b57a3d87f4e7fbb5bcb7 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -3,15 +3,14 @@ import sys from typing import List, Dict, Any # 第三方库导入 -from PySide6.QtCore import Qt, QTimer -from PySide6.QtGui import QIcon, QKeySequence, QScreen, QShortcut +from PySide6.QtCore import Qt +from PySide6.QtGui import QIcon, QKeySequence, QShortcut from PySide6.QtWidgets import ( - QApplication, QFileDialog, QHBoxLayout, QLabel, QSplitter, QVBoxLayout, QWidget + QApplication, QLabel ) from qfluentwidgets import FluentIcon, FluentWindow, NavigationItemPosition, qrouter, FluentTitleBar, ToolButton, setTheme, Theme, isDarkTheme # 项目模块导入 -from core import get_color_info from core import get_config_manager, ImageMediator from utils import tr, get_locale_manager from version import version_manager @@ -23,10 +22,21 @@ from .palette_management import PaletteManagementInterface from .preset_color import PresetColorInterface from .settings import SettingsInterface from .color_preview import ColorPreviewInterface -from .cards import ColorCardPanel -from .histograms import LuminanceHistogramWidget, RGBHistogramWidget from .color_wheel import HSBColorWheel, InteractiveColorWheel -from .canvases import ImageCanvas, LuminanceCanvas + +# 工具按钮统一样式 +_TOOLBUTTON_STYLE = """ + ToolButton { + background-color: transparent !important; + border: none !important; + } + ToolButton:hover { + background-color: rgba(128, 128, 128, 30) !important; + } + ToolButton:pressed { + background-color: rgba(128, 128, 128, 50) !important; + } +""" class CustomTitleBar(FluentTitleBar): @@ -39,18 +49,7 @@ class CustomTitleBar(FluentTitleBar): self.themeButton = ToolButton(self) self.themeButton.setFixedSize(40, 32) self.themeButton.setToolTip(tr('title_bar.toggle_theme')) - self.themeButton.setStyleSheet(""" - ToolButton { - background-color: transparent !important; - border: none !important; - } - ToolButton:hover { - background-color: rgba(128, 128, 128, 30) !important; - } - ToolButton:pressed { - background-color: rgba(128, 128, 128, 50) !important; - } - """) + self.themeButton.setStyleSheet(_TOOLBUTTON_STYLE) self._update_theme_icon() # 连接点击事件 @@ -60,18 +59,7 @@ class CustomTitleBar(FluentTitleBar): self.fullscreenButton = ToolButton(self) self.fullscreenButton.setFixedSize(40, 32) self.fullscreenButton.setToolTip(tr('title_bar.toggle_fullscreen')) - self.fullscreenButton.setStyleSheet(""" - ToolButton { - background-color: transparent !important; - border: none !important; - } - ToolButton:hover { - background-color: rgba(128, 128, 128, 30) !important; - } - ToolButton:pressed { - background-color: rgba(128, 128, 128, 50) !important; - } - """) + self.fullscreenButton.setStyleSheet(_TOOLBUTTON_STYLE) self._update_fullscreen_icon() # 连接点击事件 diff --git a/ui/palette_management.py b/ui/palette_management.py index 8565a1e49e73157277c08a4fe8a061a89674598f..d49a7909fa72a76c2b9e32aa0b21549b76dcd93a 100644 --- a/ui/palette_management.py +++ b/ui/palette_management.py @@ -1,6 +1,7 @@ # 标准库导入 import math from datetime import datetime +from pathlib import Path from typing import List, Dict, Any # 第三方库导入 @@ -11,18 +12,18 @@ from PySide6.QtWidgets import ( ) from qfluentwidgets import ( CardWidget, ScrollArea, ToolButton, FluentIcon, ComboBox, - InfoBar, InfoBarPosition, isDarkTheme, qconfig, + InfoBar, InfoBarPosition, qconfig, PushButton, SubtitleLabel, MessageBox ) # 项目模块导入 from core import get_color_info, hex_to_rgb, get_config_manager, ServiceFactory -from utils import tr, get_locale_manager, calculate_grid_columns +from utils import tr, get_locale_manager, calculate_grid_columns, get_default_data_directory, get_last_directory, set_last_directory from core.async_loader import BaseBatchLoader from core.grouping import generate_groups from core.logger import get_logger, log_user_action, log_performance from .cards import ColorModeContainer, get_text_color, get_border_color, get_placeholder_color -from utils.theme_colors import get_card_background_color, get_title_color, get_interface_background_color +from utils.theme_colors import get_title_color from dialogs import ColorblindPreviewDialog, ContrastCheckDialog, EditPaletteDialog logger = get_logger("palette_management") @@ -1285,13 +1286,14 @@ class PaletteManagementInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('palette_management.import_title'), - "", + get_last_directory("palette_import", get_default_data_directory()), tr('palette_management.json_filter') ) if not file_path: return + set_last_directory("palette_import", str(Path(file_path).parent)) log_user_action("import_palette_start", {"file_path": file_path}) self._pending_import_path = file_path @@ -1365,7 +1367,8 @@ class PaletteManagementInterface(QWidget): def _on_export_clicked(self): """导出按钮点击""" - default_name = f"color_card_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + last_dir = get_last_directory("palette_export", get_default_data_directory()) + default_name = str(Path(last_dir) / f"color_card_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json") file_path, _ = QFileDialog.getSaveFileName( self, tr('palette_management.export_title'), @@ -1379,6 +1382,7 @@ class PaletteManagementInterface(QWidget): if not file_path.endswith('.json'): file_path += '.json' + set_last_directory("palette_export", str(Path(file_path).parent)) log_user_action("export_palette_start", {"file_path": file_path}) favorites = self._config_manager.get_favorites() @@ -1442,7 +1446,8 @@ class PaletteManagementInterface(QWidget): return palette_name = favorite_data.get('name', tr('palette_management.unnamed')) - default_name = f"{palette_name}.ase" + last_dir = get_last_directory("palette_export", get_default_data_directory()) + default_name = str(Path(last_dir) / f"{palette_name}.ase") file_path, _ = QFileDialog.getSaveFileName( self, @@ -1457,6 +1462,7 @@ class PaletteManagementInterface(QWidget): if not file_path.endswith('.ase'): file_path += '.ase' + set_last_directory("palette_export", str(Path(file_path).parent)) log_user_action("export_ase_start", {"file_path": file_path, "palette_name": palette_name}) with log_performance("export_ase", {"file_path": file_path, "color_count": len(colors)}): diff --git a/ui/preset_color.py b/ui/preset_color.py index ba0c493fe0ed167fd7053b5b777a4b5ba4e047b7..735ac25c179d6678444bd27b3a62adb7e17df8f0 100644 --- a/ui/preset_color.py +++ b/ui/preset_color.py @@ -2,7 +2,7 @@ import math import uuid from datetime import datetime -from typing import List, Dict, Any +from typing import Dict, Any # 第三方库导入 from PySide6.QtCore import Qt, Signal @@ -23,7 +23,7 @@ from core.color_data import ( get_color_source, get_all_color_sources, get_random_palettes, ColorSource ) from .cards import ColorModeContainer, get_text_color, get_border_color, get_placeholder_color -from utils.theme_colors import get_card_background_color, get_title_color, get_interface_background_color, get_secondary_text_color +from utils.theme_colors import get_title_color, get_secondary_text_color # ============================================================================= diff --git a/ui/settings.py b/ui/settings.py index 49ee4c49b4abf03893d0fe0ba236e519d83e6b14..35dea45d7f8fe081fe8e292adcf218bf35b230a3 100644 --- a/ui/settings.py +++ b/ui/settings.py @@ -1,23 +1,21 @@ # 标准库导入 from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import ( - QFileDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget + QHBoxLayout, QLabel, QVBoxLayout, QWidget ) from qfluentwidgets import ( - ComboBox, FluentIcon, InfoBar, InfoBarPosition, - PushButton, PushSettingCard, ScrollArea, SettingCardGroup, SubtitleLabel, SwitchButton, qconfig, isDarkTheme + ComboBox, FluentIcon, PushSettingCard, ScrollArea, SettingCardGroup, SubtitleLabel, SwitchButton, qconfig ) # 项目模块导入 from core import get_config_manager from core.logger import get_logger, log_user_action from utils import tr, get_supported_languages, set_language, get_locale_manager - +from utils.theme_colors import get_title_color from dialogs import AboutDialog, UpdateAvailableDialog +from version import version_manager logger = get_logger("settings") -from version import version_manager -from utils.theme_colors import get_title_color, get_text_color, get_interface_background_color, get_card_background_color, get_border_color AVAILABLE_COLOR_MODES = ['HSB', 'LAB', 'HSL', 'CMYK', 'RGB'] @@ -240,12 +238,17 @@ class SettingsInterface(QWidget): card.button.setVisible(False) combo_box = ComboBox(self.content_widget) + + combo_box.addItem(tr('settings.language_auto')) + combo_box.setItemData(0, 'auto') + supported_languages = get_supported_languages() for code, name in supported_languages.items(): + if code == 'auto': + continue combo_box.addItem(name) combo_box.setItemData(combo_box.count() - 1, code) - # 设置当前语言 for i in range(combo_box.count()): if combo_box.itemData(i) == self._language: combo_box.setCurrentIndex(i) @@ -297,6 +300,8 @@ class SettingsInterface(QWidget): # 更新语言卡片 self.language_card.titleLabel.setText(tr('settings.language_title')) self.language_card.contentLabel.setText(tr('settings.language_desc')) + # 更新"跟随系统"选项文本 + self.language_card.combo_box.setItemText(0, tr('settings.language_auto')) # 更新16进制显示卡片 self.hex_display_card.titleLabel.setText(tr('settings.hex_display')) diff --git a/ui/zoom_viewer.py b/ui/zoom_viewer.py index 97057470860ac34188a3b26edc7b5ceac3ea62c4..59a63eb477cf59ad998e265b2b81050e285936f5 100644 --- a/ui/zoom_viewer.py +++ b/ui/zoom_viewer.py @@ -1,6 +1,6 @@ # 第三方库导入 from PySide6.QtCore import QPoint, Qt -from PySide6.QtGui import QColor, QImage, QPainter, QPainterPath, QPen +from PySide6.QtGui import QPainter, QPainterPath, QPen from PySide6.QtWidgets import QWidget # 项目模块导入 diff --git a/utils/__init__.py b/utils/__init__.py index 622ca684af0022eeb2f8a82e086f87762233e3e4..18b19b4a63ecec1da0e5185b9e49777b7eca1baf 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,6 +1,13 @@ """工具函数模块""" -from .icon import load_icon_universal, get_icon_path, create_fallback_icon +# 标准库导入 +from pathlib import Path + +# 第三方库导入 +from PySide6.QtCore import QSettings + +# 项目模块导入 +from .icon import load_icon_universal, get_icon_path, create_fallback_icon, get_base_path from .platform import set_app_user_model_id, fix_windows_taskbar_icon_for_window, set_window_title_bar_theme from .layout import calculate_grid_columns from .locale import ( @@ -12,10 +19,58 @@ from .locale import ( get_supported_languages, ) + +def get_default_image_directory() -> str: + """获取默认图片导入目录 + + Returns: + str: 用户图片文件夹路径 + """ + return str(Path.home() / "Pictures") + + +def get_default_data_directory() -> str: + """获取默认数据文件目录(用于导入/导出配色数据) + + Returns: + str: 用户文档文件夹路径 + """ + return str(Path.home() / "Documents") + + +def get_last_directory(key: str, default_dir: str) -> str: + """获取用户上次选择的目录 + + Args: + key: 存储键名(区分不同功能) + default_dir: 默认目录(首次使用或记录不存在时返回) + + Returns: + str: 上次选择的目录或默认目录 + """ + settings = QSettings("ColorCard", "App") + last_dir = settings.value(f"last_directory/{key}", default_dir) + if last_dir and Path(last_dir).exists(): + return str(last_dir) + return default_dir + + +def set_last_directory(key: str, directory: str): + """记录用户选择的目录 + + Args: + key: 存储键名 + directory: 用户选择的目录路径 + """ + settings = QSettings("ColorCard", "App") + settings.setValue(f"last_directory/{key}", directory) + + __all__ = [ 'load_icon_universal', 'get_icon_path', 'create_fallback_icon', + 'get_base_path', 'set_app_user_model_id', 'fix_windows_taskbar_icon_for_window', 'set_window_title_bar_theme', @@ -26,4 +81,8 @@ __all__ = [ 'set_language', 'get_current_language', 'get_supported_languages', + 'get_default_image_directory', + 'get_default_data_directory', + 'get_last_directory', + 'set_last_directory', ] diff --git a/utils/locale.py b/utils/locale.py index 2961b548a26cde6d89abc9ee9ac7af58c25c57c1..af963efdc9b4d9441a0b3a3a7df2186b3015e0e7 100644 --- a/utils/locale.py +++ b/utils/locale.py @@ -9,7 +9,26 @@ import sys from pathlib import Path from typing import Any, Dict, Optional -from PySide6.QtCore import QObject, Signal +from PySide6.QtCore import QLocale, QObject, Signal + + +SYSTEM_LANGUAGE_MAPPING: Dict[str, str] = { + 'zh_CN': 'ZW_JT', + 'zh_Hans': 'ZW_JT', + 'zh': 'ZW_JT', + 'zh_TW': 'ZW_FT', + 'zh_HK': 'ZW_FT', + 'zh_Hant': 'ZW_FT', + 'en': 'EN_US', + 'en_US': 'EN_US', + 'en_GB': 'EN_US', + 'ja': 'JA_JP', + 'ja_JP': 'JA_JP', + 'fr': 'FR_FR', + 'fr_FR': 'FR_FR', + 'ru': 'RU_RU', + 'ru_RU': 'RU_RU', +} def _get_base_path() -> str: @@ -38,6 +57,7 @@ class LocaleManager(QObject): language_changed = Signal(str) SUPPORTED_LANGUAGES = { + 'auto': '跟随系统', 'ZW_JT': '简体中文', 'ZW_FT': '繁體中文', 'EN_US': 'English', @@ -46,7 +66,8 @@ class LocaleManager(QObject): 'RU_RU': 'Русский' } - DEFAULT_LANGUAGE = 'ZW_JT' + DEFAULT_LANGUAGE = 'auto' + FALLBACK_LANGUAGE = 'ZW_JT' def __init__(self): """初始化多语言管理器""" @@ -68,19 +89,21 @@ class LocaleManager(QObject): """加载指定语言的翻译数据 Args: - language_code: 语言代码(如 'ZW_JT', 'EN_US') + language_code: 语言代码(如 'ZW_JT', 'EN_US', 'auto') Returns: bool: 是否加载成功 """ - if language_code not in self.SUPPORTED_LANGUAGES: - language_code = self.DEFAULT_LANGUAGE + resolved_code = self.resolve_language(language_code) + + if resolved_code not in self.SUPPORTED_LANGUAGES: + resolved_code = self.FALLBACK_LANGUAGE - locale_file = self._locales_dir / f'{language_code}.json' + locale_file = self._locales_dir / f'{resolved_code}.json' if not locale_file.exists(): - if language_code != self.DEFAULT_LANGUAGE: - return self.load_language(self.DEFAULT_LANGUAGE) + if resolved_code != self.FALLBACK_LANGUAGE: + return self.load_language(self.FALLBACK_LANGUAGE) return False try: @@ -91,6 +114,36 @@ class LocaleManager(QObject): except (json.JSONDecodeError, IOError, OSError): return False + def resolve_language(self, language_code: str) -> str: + """解析语言代码,如果是 'auto' 则返回系统语言 + + Args: + language_code: 语言代码(如 'auto', 'ZW_JT', 'EN_US') + + Returns: + str: 实际的语言代码 + """ + if language_code == 'auto': + return self.get_system_language() + return language_code + + def get_system_language(self) -> str: + """获取系统语言并映射到项目支持的语言代码 + + Returns: + str: 映射后的语言代码,未匹配则返回默认语言 + """ + try: + system_locale = QLocale.system().name() + if system_locale in SYSTEM_LANGUAGE_MAPPING: + return SYSTEM_LANGUAGE_MAPPING[system_locale] + base_locale = system_locale.split('_')[0] + if base_locale in SYSTEM_LANGUAGE_MAPPING: + return SYSTEM_LANGUAGE_MAPPING[base_locale] + except (AttributeError, ValueError): + pass + return self.FALLBACK_LANGUAGE + def set_language(self, language_code: str) -> bool: """设置当前语言 diff --git a/utils/platform.py b/utils/platform.py index c95695f659f158863e20853ef1d1bca7c852741a..17d6364ade234fdd6ea2cf96042352003695546c 100644 --- a/utils/platform.py +++ b/utils/platform.py @@ -5,7 +5,7 @@ import sys from typing import Dict, Optional # 第三方库导入 -from PySide6.QtCore import QObject, Qt, QTimer, Signal +from PySide6.QtCore import QObject, QTimer, Signal # 项目模块导入 from .icon import get_icon_path @@ -145,7 +145,8 @@ def fix_windows_taskbar_icon_for_window(window) -> bool: try: # 确保窗口已经显示 - if not window.isVisible(): + # 注意:全屏窗口的 isVisible 可能返回 False,需要特殊处理 + if not window.isVisible() and not window.isFullScreen(): window.show() window.raise_() window.activateWindow() diff --git a/utils/theme_colors.py b/utils/theme_colors.py index 50d7137ac7dd2bd05830368afa8e90506a8c19c3..b468a8920fa1c0410c724225baee8b5a9a66b4d5 100644 --- a/utils/theme_colors.py +++ b/utils/theme_colors.py @@ -12,16 +12,6 @@ def get_canvas_background_color(): return QColor(42, 42, 42) -def get_card_background_color(): - """获取卡片背景颜色""" - return QColor(42, 42, 42) if isDarkTheme() else QColor(255, 255, 255) - - -def get_interface_background_color(): - """获取界面背景颜色(与FluentWindow一致)""" - return QColor(32, 32, 32) if isDarkTheme() else QColor(243, 243, 243) - - def get_histogram_background_color(): """获取直方图背景颜色 - 固定灰黑色 #2a2a2a""" return QColor(42, 42, 42) @@ -52,11 +42,6 @@ def get_border_color(): return QColor(80, 80, 80) if isDarkTheme() else QColor(221, 221, 221) -def get_border_color_secondary(): - """获取次要边框颜色""" - return QColor(120, 120, 120) if isDarkTheme() else QColor(200, 200, 200) - - # ========== 占位符/空状态颜色 ========== def get_placeholder_color(): """获取占位符颜色(空色块背景)""" @@ -201,36 +186,11 @@ def get_canvas_empty_text_color(): return QColor(150, 150, 150) -def get_picker_colors(): - """获取取色点颜色列表""" - return [ - QColor(0, 102, 255, 100), - QColor(0, 128, 255, 100), - QColor(0, 153, 255, 100), - QColor(0, 204, 102, 100), - QColor(102, 255, 102, 100), - QColor(255, 204, 0, 100), - QColor(255, 128, 0, 100), - QColor(255, 51, 102, 100), - QColor(200, 100, 255, 100), - ] - - def get_tooltip_bg_color(): """获取提示框背景颜色""" return QColor(0, 0, 0, 180) -def get_tooltip_border_color(): - """获取提示框边框颜色""" - return QColor(255, 255, 255) - - -def get_tooltip_text_color(): - """获取提示框文本颜色""" - return QColor(0, 0, 0) - - # ========== 缩放查看器颜色 ========== def get_zoom_grid_color(): """获取缩放查看器网格颜色""" @@ -248,6 +208,21 @@ def get_dialog_bg_color(): return QColor(32, 32, 32) if isDarkTheme() else QColor(255, 255, 255) +def get_close_button_hover_bg_color(): + """获取关闭按钮悬停背景颜色""" + return QColor(196, 43, 28) if isDarkTheme() else QColor(232, 17, 35) + + +def get_close_button_hover_color(): + """获取关闭按钮悬停图标颜色""" + return QColor(255, 255, 255) + + +def get_close_button_pressed_color(): + """获取关闭按钮按下图标颜色""" + return QColor(255, 255, 255) + + # ========== Zone框颜色 ========== def get_zone_background_color(): """获取Zone框背景颜色""" @@ -259,12 +234,6 @@ def get_zone_text_color(): return QColor(255, 255, 255) if isDarkTheme() else QColor(0, 0, 0) -# ========== 收藏组件颜色 ========== -def get_favorite_icon_color(): - """获取收藏界面图标颜色""" - return QColor(153, 153, 153) - - # ========== 高饱和度区域高亮颜色 ========== def get_high_saturation_highlight_color(): """获取高饱和度区域高亮颜色 - 半透明品红色""" diff --git a/version.py b/version.py index a22ae180cb398401c6e642542a7e676931d2bcff..77daee7a1529f9fb5f83eb9937f5fccca2f33eab 100644 --- a/version.py +++ b/version.py @@ -8,8 +8,8 @@ class VersionManager: """初始化版本管理器""" # 版本号组件 self.major: int = 1 - self.minor: int = 5 - self.patch: int = 1 + self.minor: int = 6 + self.patch: int = 0 self.build: int = 0 self.prerelease: str = "" diff --git a/version.txt b/version.txt index ab7dd437beb6065f1915ee9fdc567fca700d6930..5f427fb704e5e698446f99090c5113c7a1a032df 100644 --- a/version.txt +++ b/version.txt @@ -1,6 +1,6 @@ -1.5.1 -2026.3.15.1 -1.5.1.0 +1.6.0 +2026.3.22.1 +1.6.0.0 浮晓 HXiao Studio © 2026 浮晓 HXiao Studio 取色卡 - Color Card \ No newline at end of file diff --git a/version_info.txt b/version_info.txt index 691c4f15d21f4e7952ef9e5c9022e7ca673a85e4..484e954b603070ab5596c46162da6ccf9ecf7cb1 100644 --- a/version_info.txt +++ b/version_info.txt @@ -1,7 +1,7 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(2026,3,15,1), - prodvers=(1,5,1,0), + filevers=(2026,3,22,1), + prodvers=(1,6,0,0), mask=0x3f, flags=0x0, OS=0x4, @@ -17,12 +17,12 @@ VSVersionInfo( [ StringStruct(u'CompanyName', u'浮晓 HXiao Studio'), StringStruct(u'FileDescription', u'取色卡 - Color Card'), - StringStruct(u'FileVersion', u'1.5.1'), + StringStruct(u'FileVersion', u'1.6.0'), StringStruct(u'InternalName', u'Color_Card'), StringStruct(u'LegalCopyright', u'© 2026 浮晓 HXiao Studio'), StringStruct(u'OriginalFilename', u'Color_Card.exe'), StringStruct(u'ProductName', u'取色卡'), - StringStruct(u'ProductVersion', u'1.5.1'), + StringStruct(u'ProductVersion', u'1.6.0'), StringStruct(u'Comments', u'一站式的图片的图片分析和配色工具') ] ) diff --git a/website/changelog.html b/website/changelog.html index 5f052429f2e3f00e4b21aeab9d82a8ff773e0fa5..e94a138e2b66bdafeae2121bf9e6a025321d786f 100644 --- a/website/changelog.html +++ b/website/changelog.html @@ -171,6 +171,7 @@ 项目起源 下载 更新日志 + 使用说明 反馈 关于我们