diff --git a/installer_python-version.sh b/installer_python-version.sh new file mode 100755 index 0000000..710f59d --- /dev/null +++ b/installer_python-version.sh @@ -0,0 +1,278 @@ +#!/bin/bash + +welcome() { + echo " + ╔═══════════════════════════════════════╗ + ║ ║ + ║ ~ NovaNav Browser ~ ║ + ║ Developed with ❤️ by ║ + ║ Felipe Alfonso González L. ║ + ║ Computer Science Engineer ║ + ║ Chile ║ + ║ ║ + ║ Contact: f.alfonso@res-ear.ch ║ + ║ Licensed under BSD 3-clause ║ + ║ GitHub: github.com/felipealfonsog ║ + ║ ║ + ╚═══════════════════════════════════════╝ + " + echo "Welcome to the NovaNav Browser - Bash installer!" + echo "---------------------------------------------------------------------" +} + +check_execute_permission() { + if [[ ! -x "$0" ]]; then + echo "The installer script does not have execute permission. Do you want to grant it?" + select yn in "Yes" "No"; do + case $yn in + Yes) + chmod +x "$0" + exec "$0" "$@" + ;; + No) + echo "Exiting program." + exit 0 + ;; + *) + echo "Invalid option. Please choose a valid option." + ;; + esac + done + fi +} + +check_homebrew_installation_macOS() { + if ! command -v brew &> /dev/null; then + echo "Homebrew is not installed on macOS. Do you want to install it?" + select yn in "Yes" "No"; do + case $yn in + Yes) + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + break + ;; + No) + echo "Exiting program." + exit 0 + ;; + *) + echo "Invalid option. Please choose a valid option." + ;; + esac + done + fi +} + +check_homebrew_installation_linux() { + if ! command -v brew &> /dev/null; then + echo "Homebrew/Linuxbrew is not installed on Linux. Do you want to install it?" + select yn in "Yes" "No"; do + case $yn in + Yes) + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + break + ;; + No) + echo "Exiting program." + exit 0 + ;; + *) + echo "Invalid option. Please choose a valid option." + ;; + esac + done + fi +} + +install_dependencies_linux() { + if ! command -v python3 &> /dev/null; then + echo "Python3 is not installed on Linux. Do you want to install it?" + select yn in "Yes, using Homebrew" "Yes, using package manager" "No"; do + case $yn in + "Yes, using Homebrew") + brew install python python-pip python-pyqt5 python-pyqt5-webengine + break + ;; + "Yes, using package manager") + if [[ -f /etc/arch-release ]]; then + sudo pacman -S python python-pip python-pyqt5 python-pyqt5-webengine + elif [[ -f /etc/debian_version ]]; then + sudo apt-get update && sudo apt-get install python python-pip python-pyqt5 python-pyqt5-webengine + else + echo "Unsupported Linux distribution. Please install Python3 manually, read documentation according to your distro, and re-run the installer." + exit 1 + fi + break + ;; + "No") + echo "Exiting program." + exit 0 + ;; + *) + echo "Invalid option. Please choose a valid option." + ;; + esac + done + fi +} + + +download_source_code() { + if [[ $(uname) == "Darwin" ]]; then + source_file_url="https://raw.githubusercontent.com/felipealfonsog/GitSyncMaster/main/src/python/novanav_macos.py" + source_file_name="novanav_macos.py" + elif [[ $(uname) == "Linux" ]]; then + source_file_url="https://raw.githubusercontent.com/felipealfonsog/GitSyncMaster/main/src/python/novanav_linux.py" + source_file_name="novanav_linux.py" + else + echo "Unsupported operating system. Please install manually, read documentation, and re-run the installer." + exit 1 + fi + + curl -o "$source_file_name" "$source_file_url" +} + + + +move_exec_file() { + if [[ -f "$source_file_name" ]]; then + # Transform the source file into an executable + chmod +x "$source_file_name" + + if [[ $(uname) == "Darwin" ]]; then + # Move the executable file to /usr/local/bin/ + sudo cp "$source_file_name" /usr/local/bin/ + + # Assign execution permissions if novanav doesn't exist in that location + if [[ ! -x /usr/local/bin/novanav ]]; then + sudo chmod +x /usr/local/bin/novanav + fi + else + # Copy the source file to /bin directory of each distribution + if [[ -f /etc/arch-release ]]; then + sudo cp "$source_file_name" /usr/bin/ + elif [[ -f /etc/debian_version ]]; then + sudo cp "$source_file_name" /usr/local/bin/ + else + sudo cp "$source_file_name" /usr/local/bin/ + fi + + # Move the executable file to the appropriate location + if [[ -f /etc/arch-release ]]; then + sudo mv "$source_file_name" /usr/bin/novanav + elif [[ -f /etc/debian_version ]]; then + sudo mv "$source_file_name" /usr/local/bin/novanav + else + sudo mv "$source_file_name" /usr/local/bin/novanav + fi + + # Assign execution permissions to the novanav file if it already exists in any of those locations + if [[ -x /usr/bin/novanav ]]; then + sudo chmod +x /usr/bin/novanav + elif [[ -x /usr/local/bin/novanav ]]; then + sudo chmod +x /usr/local/bin/novanav + fi + fi + else + echo "Error: File $source_file_name not found." + exit 1 + fi +} + + +install_icon_desk_file() { + # Destination directory for the files + destination_dir="/usr/share" + + # Download the icon + curl -o nnav-iconlogo.png https://raw.githubusercontent.com/felipealfonsog/NovaNav/821d0ab8103a79dcefcd2458396d72a7304660ea/src/nnav-iconlogo.png + # Install the icon to the pixmaps folder + sudo install -Dm644 -p nnav-iconlogo.png "$destination_dir/pixmaps/novanav.png" + + # Download the .desktop file + curl -o novanav.desktop https://raw.githubusercontent.com/felipealfonsog/NovaNav/main/src/novanav.desktop + # Install the .desktop file to the applications folder + sudo install -Dm644 -p novanav.desktop "$destination_dir/applications/novanav.desktop" + + # Remove the downloaded temporary files + rm nnav-iconlogo.png novanav.desktop +} + + + +configure_path() { + if [[ $(uname) == "Darwin" ]]; then + echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bash_profile + source ~/.bash_profile + else + if [[ -f ~/.bashrc ]]; then + echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc + source ~/.bashrc + elif [[ -f ~/.bash_profile ]]; then + echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bash_profile + source ~/.bash_profile + else + echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.profile + source ~/.profile + fi + fi +} + +reload_shell() { + if [[ $(uname) == "Darwin" ]]; then + source ~/.bash_profile + elif [[ $(uname) == "Linux" ]]; then + if [[ -f /etc/arch-release ]]; then + source ~/.bashrc + elif [[ -f /etc/debian_version ]]; then + source ~/.bashrc + else + source ~/.bashrc + fi + fi +} + +cleanup() { + if [[ -f "$source_file_name" ]]; then + rm "$source_file_name" + echo "Downloaded file '$source_file_name' has been deleted." + fi + + if [[ -f "installer.sh" ]]; then + rm "installer.sh" + echo "Installer script 'installer.sh' has been deleted." + fi + + if [[ -f "novanav" ]]; then + rm "novanav" + echo "Installer binary has been deleted." + fi + + +} + + +main() { + welcome + check_execute_permission + + if [[ $(uname) == "Darwin" ]]; then + check_homebrew_installation_macOS + elif [[ $(uname) == "Linux" ]]; then + check_homebrew_installation_linux + install_dependencies_linux + fi + + download_source_code + move_exec_file + install_icon_desk_file + configure_path + reload_shell + cleanup + + echo "--------------------------------------------------------------------------------" + echo "You can now run the program by typing 'novanav' in the terminal." + echo "If you're using Arch Linux, you can find NovaNav Browser in your program menu!." + echo "--------------------------------------------------------------------------------" +} + +main \ No newline at end of file diff --git a/src/python/novanav_linux.py b/src/python/novanav_linux.py index afe848d..8c030ea 100644 --- a/src/python/novanav_linux.py +++ b/src/python/novanav_linux.py @@ -1,12 +1,12 @@ import sys -from PyQt5.QtCore import QUrl, Qt -from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QLineEdit, QPushButton, QWidget, QTabWidget, QShortcut, QDialog, QHBoxLayout, QLabel -from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage, QWebEngineSettings, QWebEngineProfile -from PyQt5.QtGui import QIcon, QPixmap +from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QTabWidget, QLineEdit, QPushButton, QDialog, QHBoxLayout, QLabel, QShortcut, QMenu, QTabBar +from PyQt5.QtGui import QKeySequence +from PyQt5.QtCore import Qt, QUrl +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEngineSettings class URLInputDialog(QDialog): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super(URLInputDialog, self).__init__(parent) self.setWindowTitle("Enter URL") layout = QVBoxLayout(self) self.url_entry = QLineEdit() @@ -15,11 +15,12 @@ def __init__(self): layout.addWidget(self.ok_button) self.ok_button.clicked.connect(self.accept) + class NovaNav(QMainWindow): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super(NovaNav, self).__init__(parent) self.setWindowTitle("NovaNav - Super Lightweight Browser") - self.setGeometry(100, 100, 800, 600) + self.setGeometry(100, 100, 800, 900) self.central_widget = QWidget() self.setCentralWidget(self.central_widget) @@ -27,15 +28,11 @@ def __init__(self): self.tab_widget = QTabWidget() self.tab_widget.setTabsClosable(True) - self.tab_widget.tabCloseRequested.connect(self.close_tab) - self.tab_widget.setMinimumWidth(200) # Set a minimum width for the tabs - self.layout.addWidget(self.tab_widget) - self.url_input_dialog = URLInputDialog() - self.url_input_dialog.ok_button.clicked.connect(self.open_new_tab) + self.url_input_dialog = URLInputDialog(self) - self.shortcut_new_tab = QShortcut("Ctrl+T", self) + self.shortcut_new_tab = QShortcut(QKeySequence("Ctrl+T"), self) self.shortcut_new_tab.activated.connect(self.show_url_input_dialog) self.shortcut_zoom_in = QShortcut(Qt.CTRL + Qt.Key_Plus, self) @@ -44,73 +41,132 @@ def __init__(self): self.shortcut_zoom_out = QShortcut(Qt.CTRL + Qt.Key_Minus, self) self.shortcut_zoom_out.activated.connect(self.zoom_out) - self.shortcut_toggle_titles = QShortcut(Qt.CTRL + Qt.Key_V, self) # Change shortcut to Ctrl+V + self.shortcut_toggle_titles = QShortcut(Qt.CTRL + Qt.Key_V, self) self.shortcut_toggle_titles.activated.connect(self.toggle_titles) self.create_new_tab("https://www.google.com") - # Set permissions and settings - self.set_permissions_and_settings() - - # Add navigation buttons self.add_navigation_buttons() - - # Establece la altura mínima de la ventana principal - self.setMinimumHeight(800) # Ajusta la altura mínima según preferencia + + self.tab_widget.tabCloseRequested.connect(self.close_tab) + + shortcut_quit = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q), self) + shortcut_quit.activated.connect(QApplication.quit) + + self.tab_widget.tabBarClicked.connect(self.addCloseButtonToTab) + + def set_tab_title(self, index, title): + # Limitar la longitud del título a 20 caracteres + title = title[:20] if len(title) > 20 else title + self.tab_widget.setTabText(index, title) + + def show_url_input_dialog(self): + if self.url_input_dialog.exec_() == QDialog.Accepted: + url = self.url_input_dialog.url_entry.text() + if not url.startswith("http://") and not url.startswith("https://"): + url = "http://" + url + self.create_new_tab(url) + + def close_tab(self, index): + widget_to_remove = self.tab_widget.widget(index) + browser_to_remove = widget_to_remove.findChild(QWebEngineView) + + if browser_to_remove: + browser_to_remove.page().disconnect() + browser_to_remove.close() + + self.tab_widget.removeTab(index) + + def zoom_in(self): + current_browser = self.tab_widget.currentWidget().findChild(QWebEngineView) + if current_browser: + current_browser.setZoomFactor(current_browser.zoomFactor() + 0.1) + + def zoom_out(self): + current_browser = self.tab_widget.currentWidget().findChild(QWebEngineView) + if current_browser: + current_browser.setZoomFactor(current_browser.zoomFactor() - 0.1) + + def set_tab_title(self, index, title): + # Limitar la longitud del título a 20 caracteres + title = title[:20] if len(title) > 20 else title + self.tab_widget.setTabText(index, title) + + def toggle_titles(self): + for i in range(self.tab_widget.count()): + browser = self.tab_widget.widget(i).findChild(QWebEngineView) + if browser: + title = browser.title() if self.tab_widget.tabBar().isVisible() else browser.page().url().toString() + self.tab_widget.setTabText(i, title) + self.tab_widget.tabBar().setVisible(not self.tab_widget.tabBar().isVisible()) + + def create_new_tab(self, url): + profile_name = f"CustomProfile_{len(self.tab_widget)}" + profile = QWebEngineProfile(profile_name) + + profile.setHttpUserAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36") + + browser = QWebEngineView() + browser.setUrl(QUrl(url)) + browser.page().settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True) + + self.tab_widget.addTab(browser, "") + + browser.loadFinished.connect(lambda: self.set_tab_title(self.tab_widget.indexOf(browser), browser.title())) + browser.urlChanged.connect(lambda: self.set_tab_title(self.tab_widget.indexOf(browser), browser.title())) + + browser.setZoomFactor(0.67) + + close_button = QPushButton("✖") + close_button.setFixedSize(10, 10) + close_button.setStyleSheet("QPushButton { background-color: transparent; border: none; font-size: 10px; color: black; }") + close_button.clicked.connect(lambda: self.close_tab(self.tab_widget.indexOf(browser))) + self.tab_widget.tabBar().setTabButton(self.tab_widget.count() - 1, QTabBar.RightSide, close_button) + + # Conectar el evento customContextMenuRequested al método onCustomContextMenuRequested + browser.setContextMenuPolicy(Qt.CustomContextMenu) + browser.customContextMenuRequested.connect(lambda pos, browser=browser: self.onCustomContextMenuRequested(pos, browser)) + + def addCloseButtonToTab(self, tabIndex): + if not self.tab_widget.tabBar().tabButton(tabIndex, QTabBar.RightSide): + close_button = QPushButton("✖") + close_button.setFixedSize(10, 10) + close_button.setStyleSheet("QPushButton { background-color: transparent; border: none; font-size: 10px; color: black; }") + close_button.clicked.connect(lambda: self.close_tab(tabIndex)) + self.tab_widget.tabBar().setTabButton(tabIndex, QTabBar.RightSide, close_button) def add_navigation_buttons(self): navigation_layout = QHBoxLayout() - - plus_button = QPushButton("+") - plus_button.setToolTip("Open Google") - plus_button.setMaximumWidth(20) # Adjust button size - plus_button.clicked.connect(self.open_google_tab) - - backward_button = QPushButton("<") - backward_button.setToolTip("Back") - backward_button.setMaximumWidth(20) # Adjust button size - backward_button.clicked.connect(self.go_back) - - forward_button = QPushButton(">") - forward_button.setToolTip("Forward") - forward_button.setMaximumWidth(20) # Adjust button size - forward_button.clicked.connect(self.go_forward) - - credits_button = QPushButton("=") - credits_button.setToolTip("Credits") - credits_button.setMaximumWidth(20) # Adjust button size - credits_button.clicked.connect(self.show_credits_popup) - - ctrl_v_button = QPushButton("-") - ctrl_v_button.setToolTip("Ctrl + V") - ctrl_v_button.setMaximumWidth(20) # Adjust button size - ctrl_v_button.clicked.connect(self.toggle_titles) - - navigation_layout.addWidget(plus_button) - navigation_layout.addWidget(backward_button) - navigation_layout.addWidget(forward_button) - navigation_layout.addWidget(credits_button) - navigation_layout.addWidget(ctrl_v_button) - - navigation_layout.addStretch(1) # Add stretch to push buttons to the right - - # Create a container widget to hold the navigation buttons + + buttons_info = [("+", self.open_google_tab), ("<", self.go_back), (">", self.go_forward), ("o", self.show_url_input_dialog), ("=", self.show_credits_popup), ("-", self.toggle_titles)] + + for text, action in buttons_info: + button = QPushButton(text) + button.setFixedSize(10, 10) + button.clicked.connect(action) + navigation_layout.addWidget(button) + + navigation_layout.addStretch() + navigation_container = QWidget() navigation_container.setLayout(navigation_layout) - # Insert the container widget into the tab widget's header self.tab_widget.setCornerWidget(navigation_container, Qt.TopRightCorner) + for i in range(self.tab_widget.count()): + self.addCloseButtonToTab(i) + def open_google_tab(self): self.create_new_tab("https://www.google.com") def show_credits_popup(self): credits_popup = QDialog(self) credits_popup.setWindowTitle("Credits") - credits_popup.setMinimumWidth(400) # Ajustar el ancho mínimo para mostrar bien el contenido + credits_layout = QVBoxLayout(credits_popup) - + credits_label = QLabel( + "NovaNav - Super Lightweight Browser\n\n" "Credits:\n" "Computer Science Engineer: Felipe Alfonso González\n" "GitHub: github.com/felipealfonsog\n" @@ -130,95 +186,89 @@ def show_credits_popup(self): "ctrl+t (New tab)\n" "ctrl+v (hide tabs for distraction-free)\n" "ctrl+q (quit)\n" - ) - - credits_label.setAlignment(Qt.AlignCenter) # Centrar el contenido + "\n") + credits_label.setAlignment(Qt.AlignCenter) credits_layout.addWidget(credits_label) - + credits_popup.exec_() def go_back(self): - current_browser = self.tab_widget.currentWidget() - if current_browser and current_browser.history().canGoBack(): - current_browser.back() + current_browser = self.tab_widget.currentWidget().findChild(QWebEngineView) + if current_browser: + if current_browser.history().canGoBack(): + current_browser.back() def go_forward(self): - current_browser = self.tab_widget.currentWidget() - if current_browser and current_browser.history().canGoForward(): - current_browser.forward() - - def set_permissions_and_settings(self): - settings = QWebEngineSettings.globalSettings() - settings.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True) - settings.setAttribute(QWebEngineSettings.LocalStorageEnabled, True) - settings.setAttribute(QWebEngineSettings.PluginsEnabled, True) - settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True) - settings.setAttribute(QWebEngineSettings.AutoLoadImages, True) - settings.setAttribute(QWebEngineSettings.JavascriptCanAccessClipboard, True) - settings.setAttribute(QWebEngineSettings.JavascriptCanOpenWindows, True) - settings.setAttribute(QWebEngineSettings.XSSAuditingEnabled, True) - settings.setAttribute(QWebEngineSettings.Accelerated2dCanvasEnabled, True) - settings.setAttribute(QWebEngineSettings.FullScreenSupportEnabled, True) - settings.setAttribute(QWebEngineSettings.ErrorPageEnabled, True) + current_browser = self.tab_widget.currentWidget().findChild(QWebEngineView) + if current_browser: + if current_browser.history().canGoForward(): + current_browser.forward() - def create_new_tab(self, url): - if not url.startswith("http"): - url = "http://" + url - browser = ExternalLinkHandler() - browser.setUrl(QUrl(url)) - browser.page().profile().setHttpUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36") # Customize the user agent to avoid blocking by some websites - browser.titleChanged.connect(lambda title, browser=browser: self.set_tab_title(browser, title[:20])) # Limit title to 20 characters - browser.page().fullScreenRequested.connect(lambda request: request.accept()) - browser.page().urlChanged.connect(self.handle_url_changed) - browser.page().setZoomFactor(0.65) # Set default zoom factor to 65% + def open_new_window(self, url): + new_window = QMainWindow() # Creamos una nueva instancia de la ventana + new_window.setWindowTitle("NovaNav - New Window") # Establecemos el título de la ventana + new_window.resize(800, 500) # Establecemos el tamaño de la ventana + screen_geometry = QApplication.primaryScreen().geometry() # Obtenemos la geometría de la pantalla + x = (screen_geometry.width() - new_window.width()) / 2 # Calculamos la posición x centrada + y = (screen_geometry.height() - new_window.height()) / 2 # Calculamos la posición y centrada + new_window.move(int(x), int(y)) # Movemos la ventana a la posición centrada - self.tab_widget.addTab(browser, "") + window = NovaNav(parent=new_window) + window.create_new_tab(url) # Abrimos una nueva pestaña con la URL proporcionada - def handle_url_changed(self, url): - if "_blank" in url.toString(): - current_browser = self.tab_widget.currentWidget() - if current_browser: - current_browser.setUrl(url) + new_window.setCentralWidget(window) # Establecemos la ventana central de la nueva ventana - def show_url_input_dialog(self): - self.url_input_dialog.show() + new_window.show() # Mostramos la nueva ventana - def open_new_tab(self): - url = self.url_input_dialog.url_entry.text() - if url: - self.create_new_tab(url) - self.url_input_dialog.hide() - def close_tab(self, index): - self.tab_widget.removeTab(index) + def onCustomContextMenuRequestedForNewWindow(self, pos): + new_browser = self.sender() + if not isinstance(new_browser, QWebEngineView): + return - def set_tab_title(self, browser, title): - index = self.tab_widget.indexOf(browser) - self.tab_widget.setTabText(index, title) + menu = QMenu() - def zoom_in(self): - current_browser = self.tab_widget.currentWidget() - if current_browser: - current_browser.setZoomFactor(current_browser.zoomFactor() + 0.1) + back_action = menu.addAction("Back") + forward_action = menu.addAction("Forward") + refresh_action = menu.addAction("Refresh") + credits_action = menu.addAction("Credits") - def zoom_out(self): - current_browser = self.tab_widget.currentWidget() - if current_browser: - current_browser.setZoomFactor(current_browser.zoomFactor() - 0.1) + back_action.triggered.connect(lambda: new_browser.back()) + forward_action.triggered.connect(lambda: new_browser.forward()) + refresh_action.triggered.connect(lambda: new_browser.reload()) + credits_action.triggered.connect(self.show_credits_popup) - def toggle_titles(self): - for i in range(self.tab_widget.count()): - browser = self.tab_widget.widget(i) - title = browser.title() if self.tab_widget.tabBar().isVisible() else self.tab_widget.tabText(i) - self.set_tab_title(browser, title[:20]) - self.tab_widget.tabBar().setVisible(not self.tab_widget.tabBar().isVisible()) + menu.exec(new_browser.mapToGlobal(pos)) + + def onCustomContextMenuRequested(self, pos, browser): + menu = QMenu() + + new_tab_action = menu.addAction("Open link in new tab") + new_window_action = menu.addAction("Open link in new window") + back_action = menu.addAction("Back") + forward_action = menu.addAction("Forward") + refresh_action = menu.addAction("Refresh") + show_tabs_action = menu.addAction("Show/Hide Tabs") + credits_action = menu.addAction("Credits") + + link_url = browser.page().contextMenuData().linkUrl() + if not link_url.isEmpty(): + new_tab_action.triggered.connect(lambda: self.create_new_tab(link_url.toString())) + new_window_action.triggered.connect(lambda: self.open_new_window(link_url.toString())) + else: + new_tab_action.setEnabled(False) + new_window_action.setEnabled(False) + + back_action.triggered.connect(lambda: browser.back()) + forward_action.triggered.connect(lambda: browser.forward()) + refresh_action.triggered.connect(lambda: browser.reload()) + + show_tabs_action.triggered.connect(self.toggle_titles) + credits_action.triggered.connect(self.show_credits_popup) -class ExternalLinkHandler(QWebEngineView): - def createWindow(self, windowType): - if windowType == QWebEnginePage.WebBrowserTab: - return self - return super().createWindow(windowType) + # Mostrar el menú contextual en la posición del cursor + menu.exec_(browser.mapToGlobal(pos)) if __name__ == "__main__":