diff --git a/ui/main_window.py b/ui/main_window.py index 4492ccfd..7823a081 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -6,9 +6,257 @@ - 停靠窗口设置 - 事件连接和信号处理 """ +WEB_BROWSER_PROCESS_CODE = r""" +import sys +import os +from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout +from PyQt5.QtCore import Qt, QUrl, QTimer +from PyQt5.QtGui import QPalette, QColor + +# 禁用硬件加速 +os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu --disable-software-rasterizer --disable-gpu-compositing --in-process-gpu" +os.environ["QT_WEBENGINE_DISABLE_GPU"] = "1" +os.environ["QT_OPENGL"] = "software" + +try: + from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings + WEB_ENGINE_AVAILABLE = True +except ImportError: + WEB_ENGINE_AVAILABLE = False + print("⚠️ QtWebEngine 不可用") + + +class EmbeddedWebBrowser(QWidget): + + def __init__(self, parent=None, url="https://www.baidu.com", parent_window_id=0): + super().__init__(parent) + self.setWindowTitle("Web 浏览器") + self.parent_window_id = parent_window_id + self.url = url + + # 设置窗口标志 - 如果要嵌入,使用子窗口样式 + if parent_window_id > 0: + # 使用 FramelessWindowHint 去除边框,便于嵌入 + self.setWindowFlags(Qt.FramelessWindowHint) + else: + self.setWindowFlags(Qt.Window) + + self.resize(800, 600) + + # 设置背景色 + palette = self.palette() + palette.setColor(QPalette.Window, QColor(25, 25, 27)) + self.setPalette(palette) + self.setAutoFillBackground(True) + + # 创建布局 + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + if WEB_ENGINE_AVAILABLE: + # 创建 WebEngine 视图 + self.web_view = QWebEngineView() + + # 设置样式 + self.web_view.setStyleSheet("background-color: #19191b;") + + # 禁用硬件加速 + settings = self.web_view.settings() + settings.setAttribute(QWebEngineSettings.Accelerated2dCanvasEnabled, False) + settings.setAttribute(QWebEngineSettings.WebGLEnabled, False) + settings.setAttribute(QWebEngineSettings.PluginsEnabled, False) + settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True) + settings.setAttribute(QWebEngineSettings.AutoLoadImages, True) + settings.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True) + + # 连接加载信号 + self.web_view.loadStarted.connect(self.on_load_started) + self.web_view.loadProgress.connect(self.on_load_progress) + self.web_view.loadFinished.connect(self.on_load_finished) + + layout.addWidget(self.web_view) + print("[WebEngine] WebEngine 视图已创建") + + # 延迟加载网页,确保窗口完全初始化 + QTimer.singleShot(500, lambda: self.load_url(url)) + else: + from PyQt5.QtWidgets import QLabel + label = QLabel("QtWebEngine 不可用\n请安装: pip install PyQtWebEngine") + label.setAlignment(Qt.AlignCenter) + label.setStyleSheet("color: white; font-size: 14px;") + layout.addWidget(label) + + def on_load_started(self): + print("[WebEngine] 开始加载网页...") + + def on_load_progress(self, progress): + if progress % 20 == 0: # 每20%打印一次 + print(f"[WebEngine] 加载进度: {progress}%") + + def on_load_finished(self, success): + if success: + print("[WebEngine] 网页加载成功") + else: + print("[WebEngine] 网页加载失败") + + def load_url(self, url): + if WEB_ENGINE_AVAILABLE and hasattr(self, 'web_view'): + print(f"[WebEngine] 准备加载 URL: {url}") + qurl = QUrl(url) + print(f"[WebEngine] QUrl 有效性: {qurl.isValid()}") + print(f"[WebEngine] QUrl 内容: {qurl.toString()}") + self.web_view.load(qurl) + print(f"[WebEngine] load() 方法已调用") + else: + print(f"[WebEngine] 无法加载 URL - WEB_ENGINE_AVAILABLE={WEB_ENGINE_AVAILABLE}, has_web_view={hasattr(self, 'web_view')}") + + def get_native_window_id(self): + + return int(self.winId()) + + def embed_into_parent(self): + + if self.parent_window_id > 0: + try: + print(f"[WebEngine] 开始嵌入窗口到父窗口 ID: {self.parent_window_id}") + + # Windows 平台使用 win32 API 嵌入窗口 + if sys.platform == 'win32': + import ctypes + from ctypes import wintypes + + # 获取窗口句柄 + hwnd = int(self.winId()) + parent_hwnd = self.parent_window_id + + print(f"[WebEngine] 子窗口句柄: {hwnd}") + print(f"[WebEngine] 父窗口句柄: {parent_hwnd}") + + user32 = ctypes.windll.user32 + + # 先获取父窗口大小 + rect = wintypes.RECT() + user32.GetClientRect(parent_hwnd, ctypes.byref(rect)) + width = rect.right - rect.left + height = rect.bottom - rect.top + print(f"[WebEngine] 父窗口大小: {width}x{height}") + + # 设置父窗口 + result = user32.SetParent(hwnd, parent_hwnd) + print(f"[WebEngine] SetParent 结果: {result}") + + # 设置窗口样式为子窗口 + GWL_STYLE = -16 + WS_CHILD = 0x40000000 + WS_VISIBLE = 0x10000000 + WS_CLIPCHILDREN = 0x02000000 + WS_CLIPSIBLINGS = 0x04000000 + + old_style = user32.GetWindowLongW(hwnd, GWL_STYLE) + print(f"[WebEngine] 旧窗口样式: {hex(old_style)}") + + # 设置为子窗口样式,移除边框和标题栏 + new_style = (WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS) + user32.SetWindowLongW(hwnd, GWL_STYLE, new_style) + + print(f"[WebEngine] 新窗口样式: {hex(new_style)}") + + # 调整窗口位置和大小,填充整个父窗口 + SWP_FRAMECHANGED = 0x0020 + SWP_SHOWWINDOW = 0x0040 + SWP_NOZORDER = 0x0004 + + result = user32.SetWindowPos( + hwnd, + 0, # HWND_TOP + 0, 0, # x, y + width, height, # width, height + SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOZORDER + ) + print(f"[WebEngine] SetWindowPos 结果: {result}") + + # 强制刷新窗口 + user32.UpdateWindow(hwnd) + user32.InvalidateRect(hwnd, None, True) + + print(f"[WebEngine] 已成功嵌入到父窗口 (Windows)") + return True + else: + # 其他平台使用 Qt 的方式 + from PyQt5.QtGui import QWindow + parent_window = QWindow.fromWinId(self.parent_window_id) + if parent_window: + self.windowHandle().setParent(parent_window) + print(f"[WebEngine] 已嵌入到父窗口 (Qt): {self.parent_window_id}") + return True + except Exception as e: + print(f"[WebEngine] 嵌入窗口失败: {e}") + import traceback + traceback.print_exc() + return False + + +def main(): + + # 设置标准输出编码为 UTF-8,避免 Windows GBK 编码问题 + if sys.platform == 'win32': + import io + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') + + print("=" * 60) + print("[WebEngine] 启动独立 WebEngine 进程") + print("=" * 60) + + # 创建应用 + app = QApplication(sys.argv) + + # 从命令行参数获取 URL 和窗口 ID + url = sys.argv[1] if len(sys.argv) > 1 else "https://www.baidu.com" + parent_window_id = int(sys.argv[2]) if len(sys.argv) > 2 else 0 + + print(f"[WebEngine] 参数:") + print(f" - URL: {url}") + print(f" - 父窗口 ID: {parent_window_id}") + + # 创建浏览器窗口 + browser = EmbeddedWebBrowser(url=url, parent_window_id=parent_window_id) + + # 显示窗口 + browser.show() + + # 输出窗口 ID 供父进程使用 + window_id = browser.get_native_window_id() + print(f"WINDOW_ID:{window_id}") + sys.stdout.flush() + + # 等待网页开始加载后再嵌入窗口 + if parent_window_id > 0: + # 延迟2秒,确保网页已经开始加载和渲染 + QTimer.singleShot(10, browser.embed_into_parent) + + print("[WebEngine] 进程已就绪") + print("=" * 60) + + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() +""" + import os import sys +# ==================== 禁用 QtWebEngine 硬件加速 ==================== +# 必须在导入 QtWebEngine 之前设置环境变量 +os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu --disable-software-rasterizer --disable-gpu-compositing --in-process-gpu" +os.environ["QT_WEBENGINE_DISABLE_GPU"] = "1" +os.environ["QT_OPENGL"] = "software" +print("✓ QtWebEngine 硬件加速已禁用,使用软件渲染模式") +# ==================== 结束配置 ==================== + from PyQt5.QtGui import QKeySequence, QIcon, QPalette, QColor # from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction, @@ -27,7 +275,7 @@ from ui.widgets import (CustomMeta3DWidget, CustomFileView, CustomTreeWidget, UniversalMessageDialog) from ui.icon_manager import get_icon_manager, get_icon -import yaml +# import yaml from PyQt5.QtWidgets import QDialog, QVBoxLayout, QListWidget, QPushButton, QDialogButtonBox, QCheckBox, QLabel, QHBoxLayout from PyQt5.QtCore import Qt @@ -1176,19 +1424,22 @@ class MainWindow(QMainWindow): # 帮助菜单 self.helpMenu = menubar.addMenu('帮助') self.aboutAction = self.helpMenu.addAction('关于') - self.aboutAction.triggered.connect(self.showAboutDialog) + + try: + import json + with open('new/project.json','r',encoding='utf-8')as f: + project_data = json.load(f) + version = project_data.get('version','1.0') + self.aboutAction.setText(f'关于 v{version}') + except Exception as e: + print(f"无法读取项目信息: {e}") + self.aboutAction.setText('关于 v1.0') + tool_menu = self.menuBar().addMenu("配置") plugin_config_action = tool_menu.addAction("渲染管线插件配置") plugin_config_action.triggered.connect(self.open_plugin_config_dialog) - def showAboutDialog(self): - msgBox = QMessageBox() - msgBox.setWindowTitle("关于") - msgBox.setText(f'元泰引擎系统\nMetaCore\n版本:v1.0') - msgBox.setIcon(QMessageBox.NoIcon) - msgBox.exec_() - def open_plugin_config_dialog(self): """打开插件配置对话框""" try: @@ -1512,7 +1763,12 @@ class MainWindow(QMainWindow): self.createSamplePanelAction.triggered.connect(self.world.info_panel_manager.onCreateSampleInfoPanel) self.create3DSamplePanelAction.triggered.connect(self.onCreate3DSampleInfoPanel) - self.webBrowserAction.triggered.connect(self.openWebBrowser) + #self.webBrowserAction.triggered.connect(self.openWebBrowser_win) + + if sys.platform == 'win32': + self.webBrowserAction.triggered.connect(self.openWebBrowser_win) + else: + self.webBrowserAction.triggered.connect(self.openWebBrowser) def getCreateMenuActions(self): """获取所有创建菜单动作的字典 - 供右键菜单使用""" @@ -3920,11 +4176,181 @@ class MainWindow(QMainWindow): self.world.setCurrentTool(None) print("工具栏: 取消选择工具") + def openWebBrowser_win(self): + """打开Web浏览器面板(使用独立进程避免与Panda3D冲突)""" + try: + from PyQt5.QtWidgets import QDockWidget, QWidget, QVBoxLayout + from PyQt5.QtCore import QProcess, Qt + import subprocess + import sys + + main_window = self.world.main_window + + # 尝试获取主窗口引用 + if main_window is None: + print("🔍 尝试获取主窗口引用...") + if hasattr(self.world, 'interface_manager'): + print(f" - interface_manager 存在: {self.world.interface_manager}") + if hasattr(self.world.interface_manager, 'main_window'): + main_window = self.world.interface_manager.main_window + print(f" - interface_manager.main_window: {main_window}") + + if main_window is None and hasattr(self.world, 'main_window'): + main_window = self.world.main_window + print(f" - world.main_window: {main_window}") + + if main_window is None and self.world.treeWidget: + try: + main_window = self.world.treeWidget.window() + print(f" - 从 treeWidget 获取窗口: {main_window}") + except: + pass + + if main_window is None: + print("✗ 无法获取主窗口引用") + UniversalMessageDialog.show_warning( + self, "错误", "无法获取主窗口引用", False, "确定" + ) + return None + else: + print(f"✅ 使用传入的主窗口引用: {main_window}") + + # 检查主窗口是否有效 + if not hasattr(main_window, 'addDockWidget'): + print(f"✗ 主窗口引用无效,缺少 addDockWidget 方法") + return None + + # 检查是否已经存在浏览器视图 + for element in self.world.gui_elements: + if hasattr(element, 'objectName') and element.objectName() == "WebBrowserView": + print("⚠ 浏览器视图已经存在") + element.show() + element.raise_() + return element + + print(f"🔧 创建浏览器停靠窗口(独立进程模式),父窗口: {main_window}") + + # 创建停靠窗口 + browser_dock = QDockWidget("信息面板", main_window) + browser_dock.setObjectName("WebBrowserView") + + # 创建容器 Widget + container_widget = QWidget() + container_layout = QVBoxLayout(container_widget) + container_layout.setContentsMargins(0, 0, 0, 0) + + # 创建一个占位 Widget,用于嵌入外部进程窗口 + embed_widget = QWidget() + embed_widget.setMinimumSize(400, 300) + container_layout.addWidget(embed_widget) + + browser_dock.setWidget(container_widget) + + # 添加到主窗口 + print("📍 将浏览器视图添加到主窗口") + main_window.addDockWidget(Qt.RightDockWidgetArea, browser_dock) + + # 获取嵌入窗口的 ID + embed_window_id = int(embed_widget.winId()) + print(f"🔑 嵌入窗口 ID: {embed_window_id}") + + # 启动独立进程 + print("🚀 启动独立 WebEngine 进程...") + #web_process_script = os.path.join(os.path.dirname(__file__), "web_browser_process.py") + import tempfile + + temp_dir = tempfile.gettempdir() + web_process_script = os.path.join(temp_dir, "web_browser_process_temp.py") + + with open(web_process_script, "w", encoding="utf-8") as f: + f.write(WEB_BROWSER_PROCESS_CODE) + + # ----------------------------------------------------- + # 创建 QProcess(注意:必须在 start 之前) + # ----------------------------------------------------- + # ----------------------------------------------------- + # 创建 QProcess(注意:必须在 start 之前) + # ----------------------------------------------------- + process = QProcess(browser_dock) + browser_dock.web_process = process # 保存进程引用 + + # ----------------------------------------------------- + # 输出信号 + # ----------------------------------------------------- + def on_process_output(): + output = bytes(process.readAllStandardOutput()).decode('utf-8', errors='ignore') + if output.strip(): + print(f"[WebProcess] {output.strip()}") + + def on_process_error(): + error = bytes(process.readAllStandardError()).decode('utf-8', errors='ignore') + if error.strip(): + print(f"[WebProcess Error] {error.strip()}") + + def on_process_finished(exit_code, exit_status): + print(f"⚠️ WebEngine 进程退出: {exit_code} {exit_status}") + + process.readyReadStandardOutput.connect(on_process_output) + process.readyReadStandardError.connect(on_process_error) + process.finished.connect(on_process_finished) + + # ----------------------------------------------------- + # 写入临时脚本 + # ----------------------------------------------------- + import tempfile + temp_dir = tempfile.gettempdir() + web_process_script = os.path.join(temp_dir, "web_browser_process_temp.py") + + with open(web_process_script, "w", encoding="utf-8") as f: + f.write(WEB_BROWSER_PROCESS_CODE) + + # ----------------------------------------------------- + # 启动子进程(这里顺序不能错) + # ----------------------------------------------------- + python_exe = sys.executable + url = "https://baidu.com" # 更可控的测试站点 + + print(f"🚀 启动: {python_exe} {web_process_script} {url} {embed_window_id}") + process.start(python_exe, [web_process_script, url, str(embed_window_id)]) + + if not process.waitForStarted(3000): + print("✗ 启动 WebEngine 失败") + return None + + print("✓ WebEngine 进程已启动") + + # 添加到GUI元素列表以便管理 + self.world.gui_elements.append(browser_dock) + + # 添加清理函数 + def cleanup_process(): + if hasattr(browser_dock, 'web_process') and browser_dock.web_process: + print("🧹 清理 WebEngine 进程...") + browser_dock.web_process.terminate() + if not browser_dock.web_process.waitForFinished(2000): + browser_dock.web_process.kill() + + browser_dock.destroyed.connect(cleanup_process) + + print("✓ 网页浏览器视图已创建并集成到项目中(独立进程模式)") + return browser_dock + + except Exception as e: + print(f"✗ 创建浏览器视图失败: {str(e)}") + import traceback + traceback.print_exc() + UniversalMessageDialog.show_error( + self, "错误", + f"创建浏览器视图失败:\n{str(e)}", + False, "确定" + ) + return None + def openWebBrowser(self): if not WEB_ENGINE_AVAILABLE: return None try: - from PyQt5.QtWebEngineWidgets import QWebEngineView + # from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.QtWidgets import QDockWidget from PyQt5.QtCore import QUrl import os @@ -3983,8 +4409,8 @@ class MainWindow(QMainWindow): self.web_view = QWebEngineView() # 加载百度网页 - #print("🌐 加载百度网页: https://www.baidu.com") - #self.web_view.load(QUrl("https://www.bootstrapmb.com/item/15762/preview")) + # print("🌐 加载百度网页: https://www.baidu.com") + # self.web_view.load(QUrl("https://www.bootstrapmb.com/item/15762/preview")) self.web_view.load(QUrl("https://www.baidu.com")) # 设置内容 @@ -4007,7 +4433,6 @@ class MainWindow(QMainWindow): return None - def getSampleWeatherData(self): """获取示例天气数据""" try: @@ -6281,7 +6706,17 @@ def setup_main_window(world,path = None): QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts) if sys.platform == 'win32': QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL) + + # 添加 Chromium 参数禁用硬件加速,避免与 Panda3D 的 OpenGL 冲突 + sys.argv.extend([ + '--disable-gpu', + '--disable-software-rasterizer', + '--disable-gpu-compositing', + '--disable-accelerated-2d-canvas', + '--num-raster-threads=1' + ]) app = QApplication(sys.argv) + print("✓ QApplication 已创建,QtWebEngine 硬件加速已禁用") main_window = MainWindow(world) main_window.show()