pygpt-net 2.6.56__py3-none-any.whl → 2.6.58__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +10 -0
- pygpt_net/__init__.py +1 -1
- pygpt_net/app.py +30 -25
- pygpt_net/config.py +44 -0
- pygpt_net/controller/chat/remote_tools.py +5 -3
- pygpt_net/controller/debug/debug.py +3 -3
- pygpt_net/controller/dialogs/info.py +6 -2
- pygpt_net/controller/ui/mode.py +5 -3
- pygpt_net/controller/ui/tabs.py +17 -0
- pygpt_net/core/filesystem/url.py +5 -2
- pygpt_net/core/models/models.py +2 -1
- pygpt_net/core/plugins/plugins.py +60 -0
- pygpt_net/data/config/config.json +4 -2
- pygpt_net/data/config/models.json +2 -2
- pygpt_net/data/config/settings.json +55 -2
- pygpt_net/data/js/app/ui.js +1 -1
- pygpt_net/data/js/app.min.js +2 -2
- pygpt_net/data/locale/locale.de.ini +7 -1
- pygpt_net/data/locale/locale.en.ini +12 -7
- pygpt_net/data/locale/locale.es.ini +6 -0
- pygpt_net/data/locale/locale.fr.ini +6 -0
- pygpt_net/data/locale/locale.it.ini +6 -0
- pygpt_net/data/locale/locale.pl.ini +7 -1
- pygpt_net/data/locale/locale.uk.ini +7 -1
- pygpt_net/data/locale/locale.zh.ini +6 -0
- pygpt_net/data/locale/plugin.agent.de.ini +0 -0
- pygpt_net/data/locale/plugin.agent.en.ini +0 -0
- pygpt_net/data/locale/plugin.agent.es.ini +0 -0
- pygpt_net/data/locale/plugin.agent.fr.ini +0 -0
- pygpt_net/data/locale/plugin.agent.it.ini +0 -0
- pygpt_net/data/locale/plugin.agent.pl.ini +0 -0
- pygpt_net/data/locale/plugin.agent.uk.ini +0 -0
- pygpt_net/data/locale/plugin.agent.zh.ini +0 -0
- pygpt_net/data/locale/plugin.audio_input.de.ini +0 -0
- pygpt_net/data/locale/plugin.audio_input.en.ini +0 -0
- pygpt_net/data/locale/plugin.audio_input.es.ini +0 -0
- pygpt_net/data/locale/plugin.audio_input.fr.ini +0 -0
- pygpt_net/data/locale/plugin.audio_input.it.ini +0 -0
- pygpt_net/data/locale/plugin.audio_input.pl.ini +0 -0
- pygpt_net/data/locale/plugin.audio_input.uk.ini +0 -0
- pygpt_net/data/locale/plugin.audio_input.zh.ini +0 -0
- pygpt_net/data/locale/plugin.audio_output.de.ini +0 -0
- pygpt_net/data/locale/plugin.audio_output.en.ini +0 -0
- pygpt_net/data/locale/plugin.audio_output.es.ini +0 -0
- pygpt_net/data/locale/plugin.audio_output.fr.ini +0 -0
- pygpt_net/data/locale/plugin.audio_output.it.ini +0 -0
- pygpt_net/data/locale/plugin.audio_output.pl.ini +0 -0
- pygpt_net/data/locale/plugin.audio_output.uk.ini +0 -0
- pygpt_net/data/locale/plugin.audio_output.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_api.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_api.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_api.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_api.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_api.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_api.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_api.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_api.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_custom.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_custom.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_custom.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_custom.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_custom.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_custom.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_custom.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_custom.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_files.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_files.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_files.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_files.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_files.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_files.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_files.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_files.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_history.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_history.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_history.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_history.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_history.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_history.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_history.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_history.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_serial.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_serial.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_serial.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_serial.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_serial.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_serial.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_serial.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_serial.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_system.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_system.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_system.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_system.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_system.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_system.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_system.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_system.zh.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_web.de.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_web.en.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_web.es.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_web.fr.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_web.it.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_web.pl.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_web.uk.ini +0 -0
- pygpt_net/data/locale/plugin.cmd_web.zh.ini +0 -0
- pygpt_net/data/locale/plugin.crontab.de.ini +0 -0
- pygpt_net/data/locale/plugin.crontab.en.ini +0 -0
- pygpt_net/data/locale/plugin.crontab.es.ini +0 -0
- pygpt_net/data/locale/plugin.crontab.fr.ini +0 -0
- pygpt_net/data/locale/plugin.crontab.it.ini +0 -0
- pygpt_net/data/locale/plugin.crontab.pl.ini +0 -0
- pygpt_net/data/locale/plugin.crontab.uk.ini +0 -0
- pygpt_net/data/locale/plugin.crontab.zh.ini +0 -0
- pygpt_net/data/locale/plugin.experts.de.ini +0 -0
- pygpt_net/data/locale/plugin.experts.en.ini +0 -0
- pygpt_net/data/locale/plugin.experts.es.ini +0 -0
- pygpt_net/data/locale/plugin.experts.fr.ini +0 -0
- pygpt_net/data/locale/plugin.experts.it.ini +0 -0
- pygpt_net/data/locale/plugin.experts.pl.ini +0 -0
- pygpt_net/data/locale/plugin.experts.uk.ini +0 -0
- pygpt_net/data/locale/plugin.experts.zh.ini +0 -0
- pygpt_net/data/locale/plugin.extra_prompt.de.ini +0 -0
- pygpt_net/data/locale/plugin.extra_prompt.en.ini +0 -0
- pygpt_net/data/locale/plugin.extra_prompt.es.ini +0 -0
- pygpt_net/data/locale/plugin.extra_prompt.fr.ini +0 -0
- pygpt_net/data/locale/plugin.extra_prompt.it.ini +0 -0
- pygpt_net/data/locale/plugin.extra_prompt.pl.ini +0 -0
- pygpt_net/data/locale/plugin.extra_prompt.uk.ini +0 -0
- pygpt_net/data/locale/plugin.extra_prompt.zh.ini +0 -0
- pygpt_net/data/locale/plugin.idx_llama_index.de.ini +0 -0
- pygpt_net/data/locale/plugin.idx_llama_index.en.ini +0 -0
- pygpt_net/data/locale/plugin.idx_llama_index.es.ini +0 -0
- pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +0 -0
- pygpt_net/data/locale/plugin.idx_llama_index.it.ini +0 -0
- pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +0 -0
- pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +0 -0
- pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +0 -0
- pygpt_net/data/locale/plugin.mailer.en.ini +0 -0
- pygpt_net/data/locale/plugin.mcp.en.ini +0 -0
- pygpt_net/data/locale/plugin.openai_dalle.de.ini +0 -0
- pygpt_net/data/locale/plugin.openai_dalle.en.ini +0 -0
- pygpt_net/data/locale/plugin.openai_dalle.es.ini +0 -0
- pygpt_net/data/locale/plugin.openai_dalle.fr.ini +0 -0
- pygpt_net/data/locale/plugin.openai_dalle.it.ini +0 -0
- pygpt_net/data/locale/plugin.openai_dalle.pl.ini +0 -0
- pygpt_net/data/locale/plugin.openai_dalle.uk.ini +0 -0
- pygpt_net/data/locale/plugin.openai_dalle.zh.ini +0 -0
- pygpt_net/data/locale/plugin.openai_vision.de.ini +0 -0
- pygpt_net/data/locale/plugin.openai_vision.en.ini +0 -0
- pygpt_net/data/locale/plugin.openai_vision.es.ini +0 -0
- pygpt_net/data/locale/plugin.openai_vision.fr.ini +0 -0
- pygpt_net/data/locale/plugin.openai_vision.it.ini +0 -0
- pygpt_net/data/locale/plugin.openai_vision.pl.ini +0 -0
- pygpt_net/data/locale/plugin.openai_vision.uk.ini +0 -0
- pygpt_net/data/locale/plugin.openai_vision.zh.ini +0 -0
- pygpt_net/data/locale/plugin.osm.en.ini +24 -24
- pygpt_net/data/locale/plugin.real_time.de.ini +0 -0
- pygpt_net/data/locale/plugin.real_time.en.ini +0 -0
- pygpt_net/data/locale/plugin.real_time.es.ini +0 -0
- pygpt_net/data/locale/plugin.real_time.fr.ini +0 -0
- pygpt_net/data/locale/plugin.real_time.it.ini +0 -0
- pygpt_net/data/locale/plugin.real_time.pl.ini +0 -0
- pygpt_net/data/locale/plugin.real_time.uk.ini +0 -0
- pygpt_net/data/locale/plugin.real_time.zh.ini +0 -0
- pygpt_net/data/locale/plugin.voice_control.de.ini +0 -0
- pygpt_net/data/locale/plugin.voice_control.en.ini +0 -0
- pygpt_net/data/locale/plugin.voice_control.es.ini +0 -0
- pygpt_net/data/locale/plugin.voice_control.fr.ini +0 -0
- pygpt_net/data/locale/plugin.voice_control.it.ini +0 -0
- pygpt_net/data/locale/plugin.voice_control.pl.ini +0 -0
- pygpt_net/data/locale/plugin.voice_control.uk.ini +0 -0
- pygpt_net/data/locale/plugin.voice_control.zh.ini +0 -0
- pygpt_net/data/locale/plugin.wolfram.en.ini +9 -9
- pygpt_net/js_rc.py +5 -5
- pygpt_net/plugin/base/plugin.py +3 -5
- pygpt_net/plugin/cmd_web/config.py +17 -17
- pygpt_net/plugin/cmd_web/worker.py +325 -171
- pygpt_net/provider/api/x_ai/__init__.py +2 -0
- pygpt_net/provider/core/config/patch.py +23 -1
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +1 -0
- pygpt_net/provider/llms/anthropic.py +4 -0
- pygpt_net/provider/llms/base.py +2 -0
- pygpt_net/provider/llms/deepseek_api.py +2 -0
- pygpt_net/provider/llms/google.py +2 -0
- pygpt_net/provider/llms/hugging_face_api.py +4 -0
- pygpt_net/provider/llms/hugging_face_router.py +2 -0
- pygpt_net/provider/llms/mistral.py +4 -0
- pygpt_net/provider/llms/perplexity.py +2 -0
- pygpt_net/provider/llms/x_ai.py +2 -0
- pygpt_net/tools/html_canvas/ui/widgets.py +19 -18
- pygpt_net/tools/web_browser/__init__.py +12 -0
- pygpt_net/tools/web_browser/tool.py +232 -0
- pygpt_net/tools/web_browser/ui/__init__.py +0 -0
- pygpt_net/tools/web_browser/ui/dialogs.py +123 -0
- pygpt_net/tools/web_browser/ui/widgets.py +351 -0
- pygpt_net/ui/layout/chat/output.py +5 -5
- pygpt_net/ui/widget/dialog/base.py +4 -1
- pygpt_net/ui/widget/textarea/html.py +173 -24
- pygpt_net/ui/widget/textarea/input.py +19 -3
- pygpt_net/ui/widget/textarea/web.py +2 -1
- {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.58.dist-info}/METADATA +26 -2
- {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.58.dist-info}/RECORD +59 -54
- {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.58.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.58.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.58.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2025.09.22 19:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from PySide6.QtCore import Qt, Slot, QUrl, QObject, Signal, QSize
|
|
13
|
+
from PySide6.QtGui import QIcon
|
|
14
|
+
from PySide6.QtWidgets import (
|
|
15
|
+
QVBoxLayout,
|
|
16
|
+
QHBoxLayout,
|
|
17
|
+
QLineEdit,
|
|
18
|
+
QPushButton,
|
|
19
|
+
QWidget,
|
|
20
|
+
QSizePolicy,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from pygpt_net.ui.widget.element.labels import HelpLabel
|
|
24
|
+
from pygpt_net.ui.widget.textarea.html import HtmlOutput
|
|
25
|
+
from pygpt_net.utils import trans
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ToolWidget:
|
|
29
|
+
def __init__(self, window=None, tool=None):
|
|
30
|
+
"""
|
|
31
|
+
HTML/JS canvas widget
|
|
32
|
+
|
|
33
|
+
:param window: Window instance
|
|
34
|
+
:param tool: Tool instance
|
|
35
|
+
"""
|
|
36
|
+
self.window = window
|
|
37
|
+
self.tool = tool # tool instance
|
|
38
|
+
self.output = None # output
|
|
39
|
+
|
|
40
|
+
# --- Navigation bar state ---
|
|
41
|
+
self.nav_bar = None
|
|
42
|
+
self.nav_layout = None
|
|
43
|
+
self.address_bar = None
|
|
44
|
+
self.btn_back = None
|
|
45
|
+
self.btn_next = None
|
|
46
|
+
self.btn_reload = None
|
|
47
|
+
self.btn_go = None
|
|
48
|
+
|
|
49
|
+
def on_open(self):
|
|
50
|
+
"""On open"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def on_delete(self):
|
|
54
|
+
"""On delete"""
|
|
55
|
+
if self.tool:
|
|
56
|
+
self.tool.signals.url.disconnect(self.open_url) # keep connections clean
|
|
57
|
+
try:
|
|
58
|
+
if hasattr(self.output, 'titleChanged'):
|
|
59
|
+
self.output.titleChanged.disconnect(self.on_update_title)
|
|
60
|
+
elif hasattr(self.output, 'page') and self.output.page() and hasattr(self.output.page(), 'titleChanged'):
|
|
61
|
+
self.output.page().titleChanged.disconnect(self.on_update_title)
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
if self.output:
|
|
65
|
+
self.output.on_delete()
|
|
66
|
+
|
|
67
|
+
def set_tab(self, tab):
|
|
68
|
+
"""
|
|
69
|
+
Set tab
|
|
70
|
+
|
|
71
|
+
:param tab: Tab
|
|
72
|
+
"""
|
|
73
|
+
self.output.set_tab(tab)
|
|
74
|
+
|
|
75
|
+
def setup(self, all: bool = True) -> QVBoxLayout:
|
|
76
|
+
"""
|
|
77
|
+
Setup widget body
|
|
78
|
+
|
|
79
|
+
:param all: If True, setup all widgets
|
|
80
|
+
:return: QVBoxLayout
|
|
81
|
+
"""
|
|
82
|
+
self.output = BrowserOutput(self.window)
|
|
83
|
+
if hasattr(self.output, 'titleChanged'):
|
|
84
|
+
try:
|
|
85
|
+
self.output.titleChanged.connect(self.on_update_title)
|
|
86
|
+
except TypeError:
|
|
87
|
+
pass
|
|
88
|
+
elif hasattr(self.output, 'page') and self.output.page() and hasattr(self.output.page(), 'titleChanged'):
|
|
89
|
+
try:
|
|
90
|
+
self.output.page().titleChanged.connect(self.on_update_title)
|
|
91
|
+
except TypeError:
|
|
92
|
+
pass
|
|
93
|
+
else:
|
|
94
|
+
def _emit_title_from_load(ok: bool):
|
|
95
|
+
if not ok:
|
|
96
|
+
return
|
|
97
|
+
title = ""
|
|
98
|
+
try:
|
|
99
|
+
if hasattr(self.output, 'title'):
|
|
100
|
+
title = self.output.title()
|
|
101
|
+
elif hasattr(self.output, 'page') and self.output.page():
|
|
102
|
+
title = self.output.page().title()
|
|
103
|
+
except Exception:
|
|
104
|
+
title = ""
|
|
105
|
+
if title and title.strip() and title != "about:blank":
|
|
106
|
+
self.on_update_title(title)
|
|
107
|
+
|
|
108
|
+
# ---- Navigation bar ----
|
|
109
|
+
self.nav_bar = QWidget()
|
|
110
|
+
self.nav_layout = QHBoxLayout(self.nav_bar)
|
|
111
|
+
self.nav_layout.setContentsMargins(6, 4, 6, 4) # small margins for compact look
|
|
112
|
+
self.nav_layout.setSpacing(6)
|
|
113
|
+
self.nav_bar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
|
114
|
+
|
|
115
|
+
# Icon size and fixed navbar height (to prevent taking too much space)
|
|
116
|
+
icon_size_px = 20 # base icon size for QPushButtons
|
|
117
|
+
nav_height = max(32, min(44, icon_size_px + 16)) # compact, never half-screen
|
|
118
|
+
self.nav_bar.setFixedHeight(nav_height)
|
|
119
|
+
|
|
120
|
+
# Buttons
|
|
121
|
+
self.btn_back = QPushButton()
|
|
122
|
+
self.btn_back.setToolTip("Back")
|
|
123
|
+
self.btn_back.setIcon(QIcon(":/icons/back.svg"))
|
|
124
|
+
self.btn_back.setIconSize(QSize(icon_size_px, icon_size_px))
|
|
125
|
+
self.btn_back.setFixedHeight(nav_height - 8)
|
|
126
|
+
self.btn_back.setEnabled(False)
|
|
127
|
+
self.btn_back.setAutoDefault(False) # prevent Enter from triggering this
|
|
128
|
+
try:
|
|
129
|
+
self.btn_back.setDefault(False)
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
self.btn_next = QPushButton()
|
|
134
|
+
self.btn_next.setToolTip("Next")
|
|
135
|
+
self.btn_next.setIcon(QIcon(":/icons/forward.svg"))
|
|
136
|
+
self.btn_next.setIconSize(QSize(icon_size_px, icon_size_px))
|
|
137
|
+
self.btn_next.setFixedHeight(nav_height - 8)
|
|
138
|
+
self.btn_next.setEnabled(False)
|
|
139
|
+
self.btn_next.setAutoDefault(False)
|
|
140
|
+
try:
|
|
141
|
+
self.btn_next.setDefault(False)
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
self.btn_reload = QPushButton()
|
|
146
|
+
self.btn_reload.setToolTip("Reload")
|
|
147
|
+
self.btn_reload.setIcon(QIcon(":/icons/reload.svg"))
|
|
148
|
+
self.btn_reload.setIconSize(QSize(icon_size_px, icon_size_px))
|
|
149
|
+
self.btn_reload.setFixedHeight(nav_height - 8)
|
|
150
|
+
self.btn_reload.setAutoDefault(False)
|
|
151
|
+
try:
|
|
152
|
+
self.btn_reload.setDefault(False)
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
# "Go" button
|
|
157
|
+
self.btn_go = QPushButton()
|
|
158
|
+
self.btn_go.setToolTip("Open URL")
|
|
159
|
+
self.btn_go.setIcon(QIcon(":/icons/redo.svg"))
|
|
160
|
+
self.btn_go.setIconSize(QSize(icon_size_px, icon_size_px))
|
|
161
|
+
self.btn_go.setFixedHeight(nav_height - 8)
|
|
162
|
+
self.btn_go.setAutoDefault(False) # avoid stealing Enter
|
|
163
|
+
try:
|
|
164
|
+
self.btn_go.setDefault(False)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
# Address bar (custom line edit to capture Enter and avoid default button reload)
|
|
169
|
+
self.address_bar = AddressLineEdit(on_return_callback=self._on_address_enter)
|
|
170
|
+
self.address_bar.setPlaceholderText("Enter URL and press Enter")
|
|
171
|
+
self.address_bar.setFixedHeight(nav_height - 8)
|
|
172
|
+
# Still keep the Qt signal for completeness (won't fire because we intercept the key)
|
|
173
|
+
self.address_bar.returnPressed.connect(self._on_address_enter)
|
|
174
|
+
|
|
175
|
+
# Hook up button actions
|
|
176
|
+
self.btn_back.clicked.connect(lambda: self._navigate('back'))
|
|
177
|
+
self.btn_next.clicked.connect(lambda: self._navigate('forward'))
|
|
178
|
+
self.btn_reload.clicked.connect(lambda: self._navigate('reload'))
|
|
179
|
+
self.btn_go.clicked.connect(self._on_address_enter) # "Go" triggers address open
|
|
180
|
+
|
|
181
|
+
# Build nav layout
|
|
182
|
+
self.nav_layout.addWidget(self.btn_back)
|
|
183
|
+
self.nav_layout.addWidget(self.btn_next)
|
|
184
|
+
self.nav_layout.addWidget(self.btn_reload)
|
|
185
|
+
self.nav_layout.addWidget(self.address_bar, 1)
|
|
186
|
+
self.nav_layout.addWidget(self.btn_go)
|
|
187
|
+
|
|
188
|
+
sec_label = HelpLabel(trans("tool.web_browser.security.footer"))
|
|
189
|
+
sec_label.setAlignment(Qt.AlignCenter)
|
|
190
|
+
|
|
191
|
+
bottom_layout = QHBoxLayout()
|
|
192
|
+
bottom_layout.addWidget(sec_label)
|
|
193
|
+
|
|
194
|
+
output_layout = QVBoxLayout()
|
|
195
|
+
# put navigation bar above the web output
|
|
196
|
+
output_layout.addWidget(self.nav_bar)
|
|
197
|
+
output_layout.addWidget(self.output, 1)
|
|
198
|
+
output_layout.setContentsMargins(0, 0, 0, 0)
|
|
199
|
+
|
|
200
|
+
# connect signals
|
|
201
|
+
self.output.signals.save_as.connect(
|
|
202
|
+
self.tool.handle_save_as)
|
|
203
|
+
self.output.signals.audio_read.connect(
|
|
204
|
+
self.window.controller.chat.render.handle_audio_read)
|
|
205
|
+
|
|
206
|
+
# Keep nav bar in sync with the underlying view if signals are available
|
|
207
|
+
# (urlChanged/loadStarted/loadFinished are part of QWebEngineView)
|
|
208
|
+
if hasattr(self.output, 'urlChanged'):
|
|
209
|
+
self.output.urlChanged.connect(self._on_url_changed)
|
|
210
|
+
if hasattr(self.output, 'loadStarted'):
|
|
211
|
+
try:
|
|
212
|
+
self.output.loadStarted.connect(lambda: self._update_nav_controls())
|
|
213
|
+
except TypeError:
|
|
214
|
+
pass
|
|
215
|
+
if hasattr(self.output, 'loadFinished'):
|
|
216
|
+
try:
|
|
217
|
+
self.output.loadFinished.connect(lambda ok: self._update_nav_controls())
|
|
218
|
+
except TypeError:
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
self.tool.signals.url.connect(self.open_url)
|
|
222
|
+
self.tool.signals.closed.connect(self.on_delete)
|
|
223
|
+
|
|
224
|
+
layout = QVBoxLayout()
|
|
225
|
+
layout.addLayout(output_layout, 1)
|
|
226
|
+
layout.addLayout(bottom_layout, 0)
|
|
227
|
+
return layout
|
|
228
|
+
|
|
229
|
+
@Slot(str)
|
|
230
|
+
def open_url(self, url: str):
|
|
231
|
+
"""
|
|
232
|
+
Open URL in output
|
|
233
|
+
|
|
234
|
+
:param url: URL
|
|
235
|
+
"""
|
|
236
|
+
print("Opening URL:", url)
|
|
237
|
+
if self.address_bar:
|
|
238
|
+
self.address_bar.setText(url)
|
|
239
|
+
self.output.setUrl(QUrl(url))
|
|
240
|
+
self._update_nav_controls()
|
|
241
|
+
|
|
242
|
+
# ------------------
|
|
243
|
+
# Navigation helpers
|
|
244
|
+
# ------------------
|
|
245
|
+
def _show_navbar(self, show: bool):
|
|
246
|
+
"""Show/hide the navigation bar."""
|
|
247
|
+
if self.nav_bar:
|
|
248
|
+
self.nav_bar.setVisible(show)
|
|
249
|
+
# Keep controls state up-to-date
|
|
250
|
+
self._update_nav_controls()
|
|
251
|
+
|
|
252
|
+
def _navigate(self, action: str):
|
|
253
|
+
"""Perform navigation action on the output view."""
|
|
254
|
+
if not self.output:
|
|
255
|
+
return
|
|
256
|
+
try:
|
|
257
|
+
if action == 'back' and hasattr(self.output, 'back'):
|
|
258
|
+
self.output.back()
|
|
259
|
+
elif action == 'forward' and hasattr(self.output, 'forward'):
|
|
260
|
+
self.output.forward()
|
|
261
|
+
elif action == 'reload' and hasattr(self.output, 'reload'):
|
|
262
|
+
self.output.reload()
|
|
263
|
+
finally:
|
|
264
|
+
self._update_nav_controls()
|
|
265
|
+
|
|
266
|
+
def _on_address_enter(self):
|
|
267
|
+
"""Handle Enter pressed in the address bar or clicking GO."""
|
|
268
|
+
if not self.output or not self.address_bar:
|
|
269
|
+
return
|
|
270
|
+
text = self.address_bar.text().strip()
|
|
271
|
+
if not text:
|
|
272
|
+
return
|
|
273
|
+
url = QUrl.fromUserInput(text)
|
|
274
|
+
if url.isValid():
|
|
275
|
+
self._show_navbar(True)
|
|
276
|
+
self.output.setUrl(url)
|
|
277
|
+
self._update_nav_controls()
|
|
278
|
+
|
|
279
|
+
def _on_url_changed(self, url: QUrl):
|
|
280
|
+
"""Keep address bar in sync with the view URL."""
|
|
281
|
+
if self.address_bar:
|
|
282
|
+
self.address_bar.setText(url.toString())
|
|
283
|
+
self._update_nav_controls()
|
|
284
|
+
|
|
285
|
+
def _update_nav_controls(self):
|
|
286
|
+
"""Enable/disable back/forward buttons based on history."""
|
|
287
|
+
if not self.output:
|
|
288
|
+
return
|
|
289
|
+
# Reload is generally available when navbar is visible
|
|
290
|
+
if self.btn_reload:
|
|
291
|
+
self.btn_reload.setEnabled(True)
|
|
292
|
+
# Back/forward depend on history if available
|
|
293
|
+
if self.btn_back and self.btn_next:
|
|
294
|
+
try:
|
|
295
|
+
hist = self.output.history()
|
|
296
|
+
self.btn_back.setEnabled(bool(hist and hist.canGoBack()))
|
|
297
|
+
self.btn_next.setEnabled(bool(hist and hist.canGoForward()))
|
|
298
|
+
except Exception:
|
|
299
|
+
# If history is not available, keep safe defaults
|
|
300
|
+
self.btn_back.setEnabled(False)
|
|
301
|
+
self.btn_next.setEnabled(False)
|
|
302
|
+
|
|
303
|
+
@Slot(str)
|
|
304
|
+
def on_update_title(self, title: str):
|
|
305
|
+
"""Update tab title when the page title changes."""
|
|
306
|
+
if not self.output:
|
|
307
|
+
return
|
|
308
|
+
tab = self.output.tab
|
|
309
|
+
if tab is None:
|
|
310
|
+
return
|
|
311
|
+
if self.window and title and title.strip() and title != "about:blank":
|
|
312
|
+
try:
|
|
313
|
+
self.window.controller.ui.tabs.update_title_by_tab(tab, title)
|
|
314
|
+
except Exception:
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class BrowserOutput(HtmlOutput):
|
|
319
|
+
def __init__(self, window=None):
|
|
320
|
+
"""
|
|
321
|
+
HTML canvas output
|
|
322
|
+
|
|
323
|
+
:param window: main window
|
|
324
|
+
"""
|
|
325
|
+
super(BrowserOutput, self).__init__(window)
|
|
326
|
+
self.window = window
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# --- Address bar input widget ---
|
|
330
|
+
class AddressLineEdit(QLineEdit):
|
|
331
|
+
"""
|
|
332
|
+
Custom QLineEdit that ensures Enter triggers opening the typed URL
|
|
333
|
+
and prevents default buttons (e.g., Reload) from consuming the key.
|
|
334
|
+
"""
|
|
335
|
+
def __init__(self, on_return_callback=None, *args, **kwargs):
|
|
336
|
+
super().__init__(*args, **kwargs)
|
|
337
|
+
self._on_return_callback = on_return_callback
|
|
338
|
+
|
|
339
|
+
def keyPressEvent(self, event):
|
|
340
|
+
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
|
341
|
+
# Call the assigned handler and stop propagation so default buttons won't trigger.
|
|
342
|
+
if callable(self._on_return_callback):
|
|
343
|
+
self._on_return_callback()
|
|
344
|
+
event.accept()
|
|
345
|
+
return
|
|
346
|
+
super().keyPressEvent(event)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class ToolSignals(QObject):
|
|
350
|
+
url = Signal(str) # url
|
|
351
|
+
closed = Signal() # dialog closed
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.09.
|
|
9
|
+
# Updated Date: 2025.09.22 12:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt
|
|
@@ -86,9 +86,9 @@ class Output:
|
|
|
86
86
|
nodes['icon.indexer'].setToolTip("Indexer")
|
|
87
87
|
nodes['icon.indexer'].clicked.connect(lambda: tools.get("indexer").toggle())
|
|
88
88
|
|
|
89
|
-
nodes['icon.remote_tool.web'] = IconLabel(":/icons/web_on.svg", window=self.window)
|
|
90
|
-
nodes['icon.remote_tool.web'].setToolTip(trans("icon.remote_tool.web"))
|
|
91
|
-
nodes['icon.remote_tool.web'].clicked.connect(lambda: ctrl.chat.remote_tools.toggle('web_search'))
|
|
89
|
+
# nodes['icon.remote_tool.web'] = IconLabel(":/icons/web_on.svg", window=self.window)
|
|
90
|
+
# nodes['icon.remote_tool.web'].setToolTip(trans("icon.remote_tool.web"))
|
|
91
|
+
# nodes['icon.remote_tool.web'].clicked.connect(lambda: ctrl.chat.remote_tools.toggle('web_search'))
|
|
92
92
|
|
|
93
93
|
min_policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
|
|
94
94
|
|
|
@@ -139,7 +139,7 @@ class Output:
|
|
|
139
139
|
nodes['anim.loading'].hide()
|
|
140
140
|
|
|
141
141
|
right_bar_layout = QHBoxLayout()
|
|
142
|
-
right_bar_layout.addWidget(nodes['icon.remote_tool.web'])
|
|
142
|
+
# right_bar_layout.addWidget(nodes['icon.remote_tool.web'])
|
|
143
143
|
right_bar_layout.addWidget(nodes['icon.video.capture'])
|
|
144
144
|
right_bar_layout.addWidget(nodes['icon.audio.input'])
|
|
145
145
|
right_bar_layout.addWidget(nodes['icon.audio.output'])
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtWidgets import QApplication
|
|
13
|
-
from PySide6.QtCore import QRect, QSize, QPoint, QEvent
|
|
13
|
+
from PySide6.QtCore import QRect, QSize, QPoint, QEvent, Qt
|
|
14
14
|
from PySide6.QtWidgets import QDialog
|
|
15
15
|
|
|
16
16
|
|
|
@@ -26,6 +26,9 @@ class BaseDialog(QDialog):
|
|
|
26
26
|
self.window = window
|
|
27
27
|
self.id = id
|
|
28
28
|
self.disable_geometry_store = False
|
|
29
|
+
self.setWindowFlags(
|
|
30
|
+
self.windowFlags() | Qt.WindowMaximizeButtonHint
|
|
31
|
+
)
|
|
29
32
|
|
|
30
33
|
def showEvent(self, event):
|
|
31
34
|
"""
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.09.22 19:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import re
|
|
13
13
|
|
|
14
|
-
from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent
|
|
14
|
+
from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QCoreApplication, QEventLoop, QUrl
|
|
15
15
|
from PySide6.QtWebChannel import QWebChannel
|
|
16
16
|
from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage
|
|
17
17
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
@@ -41,34 +41,132 @@ class HtmlOutput(QWebEngineView):
|
|
|
41
41
|
self.html_content = ""
|
|
42
42
|
self.meta = None
|
|
43
43
|
self.tab = None
|
|
44
|
+
self.setMouseTracking(True)
|
|
45
|
+
|
|
46
|
+
# OpenGL widgets
|
|
47
|
+
self._glwidget = None
|
|
48
|
+
self._glwidget_filter_installed = False
|
|
49
|
+
self._unloaded = False
|
|
50
|
+
self._destroyed = False
|
|
51
|
+
|
|
44
52
|
self.installEventFilter(self)
|
|
45
53
|
|
|
54
|
+
def _detach_gl_event_filter(self):
|
|
55
|
+
"""Detach OpenGL widget event filter if installed"""
|
|
56
|
+
if self._glwidget and self._glwidget_filter_installed:
|
|
57
|
+
try:
|
|
58
|
+
self._glwidget.removeEventFilter(self)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
self._on_delete_failed(e)
|
|
61
|
+
self._glwidget = None
|
|
62
|
+
self._glwidget_filter_installed = False
|
|
63
|
+
|
|
64
|
+
def _on_delete_failed(self, e):
|
|
65
|
+
"""
|
|
66
|
+
Handle delete failure
|
|
67
|
+
|
|
68
|
+
:param e: Exception instance
|
|
69
|
+
"""
|
|
70
|
+
pass
|
|
71
|
+
self.window.core.debug.log(e)
|
|
72
|
+
|
|
73
|
+
def unload(self):
|
|
74
|
+
"""Unload the current page and free resources"""
|
|
75
|
+
try:
|
|
76
|
+
self.hide()
|
|
77
|
+
p = self.page()
|
|
78
|
+
p.triggerAction(QWebEnginePage.Stop)
|
|
79
|
+
p.setUrl(QUrl("about:blank"))
|
|
80
|
+
p.history().clear()
|
|
81
|
+
p.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self._on_delete_failed(e)
|
|
84
|
+
finally:
|
|
85
|
+
self._unloaded = True
|
|
86
|
+
|
|
46
87
|
def on_delete(self):
|
|
47
88
|
"""Clean up on delete"""
|
|
89
|
+
if self._destroyed:
|
|
90
|
+
return
|
|
91
|
+
if not self._unloaded:
|
|
92
|
+
self.unload()
|
|
93
|
+
|
|
94
|
+
self.hide()
|
|
95
|
+
self._detach_gl_event_filter()
|
|
96
|
+
|
|
48
97
|
if self.finder:
|
|
49
|
-
|
|
50
|
-
|
|
98
|
+
try:
|
|
99
|
+
self.finder.disconnect()
|
|
100
|
+
except Exception as e:
|
|
101
|
+
self._on_delete_failed(e)
|
|
102
|
+
finally:
|
|
103
|
+
self.finder = None
|
|
51
104
|
|
|
52
|
-
self.tab = None
|
|
105
|
+
self.tab = None
|
|
106
|
+
|
|
107
|
+
# disconnect signals
|
|
108
|
+
try:
|
|
109
|
+
self.loadFinished.disconnect(self.on_page_loaded)
|
|
110
|
+
self.customContextMenuRequested.disconnect(self.on_context_menu)
|
|
111
|
+
self.signals.save_as.disconnect()
|
|
112
|
+
self.signals.audio_read.disconnect()
|
|
113
|
+
except Exception as e:
|
|
114
|
+
self._on_delete_failed(e)
|
|
53
115
|
|
|
54
|
-
# delete page
|
|
55
116
|
page = self.page()
|
|
56
|
-
if page:
|
|
57
|
-
if hasattr(page, 'bridge'):
|
|
58
|
-
page.bridge.deleteLater()
|
|
59
|
-
if hasattr(page, 'channel'):
|
|
60
|
-
page.channel.deleteLater()
|
|
61
|
-
if hasattr(page, 'signals') and page.signals:
|
|
62
|
-
page.signals.deleteLater()
|
|
63
|
-
page.deleteLater() # delete page
|
|
64
117
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
118
|
+
try:
|
|
119
|
+
page.triggerAction(QWebEnginePage.Stop)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
self._on_delete_failed(e)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
page.setUrl(QUrl("about:blank"))
|
|
125
|
+
except Exception as e:
|
|
126
|
+
self._on_delete_failed(e)
|
|
70
127
|
|
|
71
|
-
|
|
128
|
+
try:
|
|
129
|
+
page.history().clear()
|
|
130
|
+
except Exception as e:
|
|
131
|
+
self._on_delete_failed(e)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
page.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
self._on_delete_failed(e)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
if hasattr(page, "setWebChannel"):
|
|
140
|
+
page.setWebChannel(None)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
self._on_delete_failed(e)
|
|
143
|
+
|
|
144
|
+
prof = None
|
|
145
|
+
try:
|
|
146
|
+
prof = page.profile()
|
|
147
|
+
except Exception as e:
|
|
148
|
+
self._on_delete_failed(e)
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
if prof is not None:
|
|
152
|
+
try:
|
|
153
|
+
prof.deleteLater()
|
|
154
|
+
except Exception as e:
|
|
155
|
+
self._on_delete_failed(e)
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
self.deleteLater()
|
|
160
|
+
except Exception as e:
|
|
161
|
+
self._on_delete_failed(e)
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete)
|
|
165
|
+
QCoreApplication.processEvents(QEventLoop.AllEvents, 50)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
self._on_delete_failed(e)
|
|
168
|
+
|
|
169
|
+
self._destroyed = True
|
|
72
170
|
|
|
73
171
|
def set_tab(self, tab):
|
|
74
172
|
"""
|
|
@@ -250,11 +348,15 @@ class HtmlOutput(QWebEngineView):
|
|
|
250
348
|
:param source: source
|
|
251
349
|
:param event: event
|
|
252
350
|
"""
|
|
253
|
-
if
|
|
254
|
-
|
|
255
|
-
event.child().isWidgetType()):
|
|
351
|
+
if event.type() == QEvent.ChildAdded and source is self and event.child().isWidgetType():
|
|
352
|
+
self._detach_gl_event_filter()
|
|
256
353
|
self._glwidget = event.child()
|
|
257
|
-
|
|
354
|
+
try:
|
|
355
|
+
self._glwidget.installEventFilter(self)
|
|
356
|
+
self._glwidget_filter_installed = True
|
|
357
|
+
except Exception:
|
|
358
|
+
self._glwidget = None
|
|
359
|
+
self._glwidget_filter_installed = False
|
|
258
360
|
elif (event.type() == event.Type.MouseButtonPress):
|
|
259
361
|
if self.tab:
|
|
260
362
|
col_idx = self.tab.column_idx
|
|
@@ -287,6 +389,8 @@ class CustomWebEnginePage(QWebEnginePage):
|
|
|
287
389
|
if self.window.core.config.has("zoom"):
|
|
288
390
|
self.setZoomFactor(self.window.core.config.get("zoom"))
|
|
289
391
|
|
|
392
|
+
self.loaded = False
|
|
393
|
+
|
|
290
394
|
# bridge Python <> JavaScript
|
|
291
395
|
self.bridge = Bridge(self.window)
|
|
292
396
|
self.channel = QWebChannel(self)
|
|
@@ -305,6 +409,9 @@ class CustomWebEnginePage(QWebEnginePage):
|
|
|
305
409
|
self.parent.finder.matches = num
|
|
306
410
|
self.parent.finder.on_find_finished()
|
|
307
411
|
|
|
412
|
+
def set_loaded(self, loaded: bool = True):
|
|
413
|
+
self.loaded = loaded
|
|
414
|
+
|
|
308
415
|
def on_view_changed(self):
|
|
309
416
|
"""On view changed"""
|
|
310
417
|
zoom = self.zoomFactor()
|
|
@@ -345,6 +452,37 @@ class CustomWebEnginePage(QWebEnginePage):
|
|
|
345
452
|
"""
|
|
346
453
|
self.signals.js_message.emit(line_number, message, source_id) # handled in debug controller
|
|
347
454
|
|
|
455
|
+
def cleanup(self):
|
|
456
|
+
"""Cleanup method to release resources"""
|
|
457
|
+
self.loaded = False
|
|
458
|
+
try:
|
|
459
|
+
self.findTextFinished.disconnect()
|
|
460
|
+
self.zoomFactorChanged.disconnect()
|
|
461
|
+
self.selectionChanged.disconnect()
|
|
462
|
+
except Exception:
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
if self.bridge:
|
|
466
|
+
try:
|
|
467
|
+
self.bridge.cleanup()
|
|
468
|
+
except Exception:
|
|
469
|
+
pass
|
|
470
|
+
self.bridge = None
|
|
471
|
+
|
|
472
|
+
if self.channel:
|
|
473
|
+
try:
|
|
474
|
+
self.channel.unregisterObject("bridge")
|
|
475
|
+
except Exception:
|
|
476
|
+
pass
|
|
477
|
+
self.channel = None
|
|
478
|
+
|
|
479
|
+
if self.signals:
|
|
480
|
+
try:
|
|
481
|
+
self.signals.deleteLater()
|
|
482
|
+
except Exception:
|
|
483
|
+
pass
|
|
484
|
+
self.signals = None
|
|
485
|
+
|
|
348
486
|
|
|
349
487
|
class Bridge(QObject):
|
|
350
488
|
"""Bridge between Python and JavaScript"""
|
|
@@ -379,6 +517,17 @@ class Bridge(QObject):
|
|
|
379
517
|
"""
|
|
380
518
|
self.window.controller.chat.render.scroll = pos
|
|
381
519
|
|
|
520
|
+
def cleanup(self):
|
|
521
|
+
"""Cleanup method to release resources"""
|
|
522
|
+
if self.window:
|
|
523
|
+
try:
|
|
524
|
+
self.window = None
|
|
525
|
+
except Exception:
|
|
526
|
+
pass
|
|
527
|
+
|
|
528
|
+
# delete the bridge object
|
|
529
|
+
self.deleteLater()
|
|
530
|
+
|
|
382
531
|
|
|
383
532
|
class WebEngineSignals(QObject):
|
|
384
533
|
save_as = Signal(str, str)
|