声明:
本项目代码来自以下两个项目
PyQt 5 / PySide 2 实现 QTabWidget 的拖入拖出功能
https://github.com/akihito-takeuchi/qt-draggable-tab-widget
SimPyWeb X —— 使用PyQt5以及QWebEngineView构建网页浏览器
Bug:
存在很多问题:
1. 新拖拽的窗口无法新建标签页;
2. 旧窗口无法关闭;
......
代码:
main.py
python">from PySide6.QtCore import QUrl, QSize, QTimer
from PySide6.QtGui import QIcon, QPixmap, QAction
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtWidgets import QToolBar, QLineEdit, QProgressBar, QLabel, QMainWindow, QTabWidget, QStatusBar, QWidget
from PySide6 import QtWidgets, QtGui, QtCore
from typing_extensions import LiteralSignal = QtCore.Signal
Slot = QtCore.Slotclass TabInfo:def __init__(self, widget=None, text=None, icon=None,tool_tip=None, whats_this=None):self.widget = widgetself.text = textself.icon = iconself.tool_tip = tool_tipself.whats_this = whats_thisclass DraggableTabWidget(QtWidgets.QTabWidget):tab_widget_instances_ = []def __init__(self, parent=None):super().__init__(parent)tab_bar = DraggableTabBar(self)self.setTabBar(tab_bar)tab_bar.createWindowRequested.connect(self.createNewWindow)self.setMovable(True)self.setTabsClosable(True)DraggableTabWidget.tab_widget_instances_.append(self)def event(self, event):if event.type() == QtCore.QEvent.Type.DeferredDelete:DraggableTabWidget.tab_widget_instances_.remove(self)return super().event(event)@Slot(QtCore.QRect, TabInfo)def createNewWindow(self, win_rect, tab_info):new_window = BrowserWindow(mode="push")new_window.tabs.addTab(tab_info.widget,tab_info.icon,tab_info.text)new_window.tabs.setTabToolTip(0, tab_info.tool_tip)new_window.tabs.setTabWhatsThis(0, tab_info.whats_this)new_window.show()new_window.setGeometry(win_rect)return new_windowclass DraggableTabBar(QtWidgets.QTabBar):createWindowRequested = Signal(QtCore.QRect, TabInfo)initializing_drag_ = Falsedrag_tab_info_ = TabInfo()dragging_widget_ = Nonedef __init__(self, parent=None):super().__init__(parent)self.click_point = QtCore.QPoint()self.can_start_drag = Falseself.a = 0def mousePressEvent(self, event):cls = DraggableTabBarif event.button() == QtCore.Qt.MouseButton.LeftButton:current_index = self.tabAt(event.pos())parent = self.parent()parent.setCurrentIndex(current_index)current_widget = parent.currentWidget()cls.drag_tab_info_ = TabInfo(current_widget, self.tabText(current_index),self.tabIcon(current_index), self.tabToolTip(current_index),self.tabWhatsThis(current_index))cls.dragging_widget_ = Noneself.click_point = event.pos()self.can_start_drag = Falseself.grabMouse()super().mousePressEvent(event)def mouseReleaseEvent(self, event):cls = DraggableTabBarif event.button() == QtCore.Qt.MouseButton.LeftButton:if cls.initializing_drag_:if self.parent().indexOf(cls.drag_tab_info_.widget) <= 0:cls.dragging_widget_ = cls.drag_tab_info_.widgetcls.dragging_widget_.setParent(None)cls.dragging_widget_.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)else:cls.dragging_widget_ = self.window()cls.initializing_drag_ = Falsecls.dragging_widget_.window().raise_()else:if cls.dragging_widget_:win_rect = cls.dragging_widget_.geometry()win_rect.moveTo(event.globalPos())idx = self.parent().indexOf(cls.drag_tab_info_.widget)if idx >= 0:self.parent().removeTab(idx)self.createWindowRequested.emit(win_rect, cls.drag_tab_info_)self.destroyUnnecessaryWindow()cls.dragging_widget_ = Nonecls.drag_tab_info_ = TabInfo()self.releaseMouse()self.click_point = QtCore.QPoint()self.can_start_drag = Falsesuper().mouseReleaseEvent(event)def mouseMoveEvent(self, event):cls = DraggableTabBarif cls.drag_tab_info_.widget is None:returnif not self.can_start_drag:moved_length = (event.pos() - self.click_point).manhattanLength()self.can_start_drag = moved_length > QtWidgets.QApplication.startDragDistance()if cls.dragging_widget_:for bar_inst in cls._tabBarInstances():bar_region = bar_inst.visibleRegion()bar_region.translate(bar_inst.mapToGlobal(QtCore.QPoint(0, 0)))if bar_region.contains(event.globalPos()):if (bar_inst == self):self.startTabMove()event.accept()returnelse:self.releaseMouse()bar_inst.grabMouse()event.accept()returnwidget_rect = self.geometry()widget_rect.moveTo(0, 0)if widget_rect.contains(event.pos()):super().mouseMoveEvent(event)elif cls.dragging_widget_ is None and self.can_start_drag:# start draggingself.startDrag()event.accept()returnif cls.dragging_widget_:cls.dragging_widget_.move(event.globalPos() + QtCore.QPoint(1, 1))cls.dragging_widget_.show()def startDrag(self):cls = DraggableTabBarif self.count() > 1:parent = self.parent()idx = parent.indexOf(cls.drag_tab_info_.widget)parent.removeTab(idx)cls.drag_tab_info_.widget.setParent(None)cls.dragging_widget_ = Nonecls.initializing_drag_ = Truerelease_event = self.createMouseEvent(QtCore.QEvent.Type.MouseButtonRelease,self.mapFromGlobal(QtGui.QCursor.pos()))QtWidgets.QApplication.postEvent(self, release_event)def createMouseEvent(self, event_type, pos=QtCore.QPoint()):if pos.isNull():global_pos = QtGui.QCursor.pos()else:global_pos = self.mapToGlobal(pos)modifiers = QtWidgets.QApplication.keyboardModifiers()event = QtGui.QMouseEvent(event_type, pos, global_pos,QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.LeftButton, modifiers)return eventdef startTabMove(self):cls = DraggableTabBarglobal_pos = QtGui.QCursor.pos()pos = self.mapFromGlobal(global_pos)if cls.drag_tab_info_.widget.parent() is not None:parent = cls.drag_tab_info_.widget.parent().parent()idx = parent.indexOf(cls.drag_tab_info_.widget)parent.removeTab(idx)parent.window().hide()idx = self.tabAt(pos)self.insertCurrentTabInfo(idx)cls.dragging_widget_ = Nonecls.drag_tab_info_ = TabInfo()press_event = self.createMouseEvent(QtCore.QEvent.Type.MouseButtonPress, self.tabRect(idx).center())QtWidgets.QApplication.postEvent(self, press_event)self.destroyUnnecessaryWindow()self.window().raise_()def destroyUnnecessaryWindow(self):cls = DraggableTabBarfor bar_inst in cls._tabBarInstances():if bar_inst.count() == 0 \and (not bar_inst.isVisible() or bar_inst.parent().parent() is None):bar_inst.deleteLater()bar_inst.parent().parent().close()def insertCurrentTabInfo(self, idx):cls = DraggableTabBarparent = self.parent()parent.insertTab(idx,cls.drag_tab_info_.widget,cls.drag_tab_info_.icon,cls.drag_tab_info_.text)parent.setTabToolTip(idx, cls.drag_tab_info_.tool_tip)parent.setTabWhatsThis(idx, cls.drag_tab_info_.whats_this)parent.setCurrentWidget(cls.drag_tab_info_.widget)@classmethoddef _tabBarInstances(cls):return [w for w in QtWidgets.QApplication.allWidgets() if w.__class__ == cls]class BrowserEngineView(QWebEngineView):tabs = []def __init__(self, Main, parent=None):super(BrowserEngineView, self).__init__(parent)self.mainWindow = Maindef createWindow(self, QWebPage_WebWindowType):webview = BrowserEngineView(self.mainWindow)tab = BrowserTab(self.mainWindow)tab.browser = webviewtab.setCentralWidget(tab.browser)self.tabs.append(tab)self.mainWindow.add_new_tab(tab)return webviewclass BrowserTab(QMainWindow):def __init__(self, Main, parent=None):super(BrowserTab, self).__init__(parent)self.mainWindow = Mainself.browser = BrowserEngineView(self.mainWindow)self.browser.load(QUrl("https://cn.bing.com"))self.setCentralWidget(self.browser)self.navigation_bar = QToolBar('Navigation')self.navigation_bar.setIconSize(QSize(24, 24))self.navigation_bar.setMovable(False)self.addToolBar(self.navigation_bar)self.status_bar = QStatusBar()self.setStatusBar(self.status_bar)self.back_button = QAction(QIcon('Assets/back.png'), '后退', self)self.next_button = QAction(QIcon('Assets/forward.png'), '前进', self)self.stop_button = QAction(QIcon('Assets/stop.png'), '停止', self)self.refresh_button = QAction(QIcon('Assets/refresh.png'), '刷新', self)self.home_button = QAction(QIcon('Assets/home.png'), '主页', self)self.enter_button = QAction(QIcon('Assets/enter.png'), '转到', self)self.add_button = QAction(QIcon('Assets/new.png'), '新建标签页', self)self.ssl_label1 = QLabel(self)self.ssl_label2 = QLabel(self)self.url_text_bar = QLineEdit(self)self.url_text_bar.setMinimumWidth(300)self.progress_bar = QProgressBar()self.progress_bar.setMaximumWidth(120)self.set_button = QAction(QIcon('Assets/setting.png'), '设置', self)self.navigation_bar.addAction(self.back_button)self.navigation_bar.addAction(self.next_button)self.navigation_bar.addAction(self.stop_button)self.navigation_bar.addAction(self.refresh_button)self.navigation_bar.addAction(self.home_button)self.navigation_bar.addAction(self.add_button)self.navigation_bar.addSeparator()self.navigation_bar.addWidget(self.ssl_label1)self.navigation_bar.addWidget(self.ssl_label2)self.navigation_bar.addWidget(self.url_text_bar)self.navigation_bar.addAction(self.enter_button)self.navigation_bar.addSeparator()self.navigation_bar.addWidget(self.progress_bar)self.navigation_bar.addAction(self.set_button)self.status_icon = QLabel()self.status_icon.setScaledContents(True)self.status_icon.setMaximumHeight(24)self.status_icon.setMaximumWidth(24)self.status_icon.setPixmap(QPixmap("Assets/main.png"))self.status_label = QLabel()self.status_label.setText(self.mainWindow.version + " - SimPyWeb X")self.status_bar.addWidget(self.status_icon)self.status_bar.addWidget(self.status_label)def navigate_to_url(self):s = QUrl(self.url_text_bar.text())if s.scheme() == '':s.setScheme('http')self.browser.load(s)def navigate_to_home(self):s = QUrl("https://www.baidu.com/")self.browser.load(s)def renew_urlbar(self, s):prec = s.scheme()if prec == 'http':self.ssl_label1.setPixmap(QPixmap("Assets/unsafe.png").scaledToHeight(24))self.ssl_label2.setText(" 不安全 ")self.ssl_label2.setStyleSheet("color:red;")elif prec == 'https':self.ssl_label1.setPixmap(QPixmap("Assets/safe.png").scaledToHeight(24))self.ssl_label2.setText(" 安全 ")self.ssl_label2.setStyleSheet("color:green;")self.url_text_bar.setText(s.toString())self.url_text_bar.setCursorPosition(0)def renew_progress_bar(self, p):self.progress_bar.setValue(p)class BrowserWindow(QWidget):name = "SimPyWeb X"version = "3.0"date = "2020.1.26"def __init__(self, mode: Literal["main", "push"] = "main"):super().__init__()self.setWindowTitle(self.name + " " + self.version)self.setWindowIcon(QIcon('Assets/main.png'))self.resize(1200, 900)self.tabs = DraggableTabWidget(self)self.tabs.setTabsClosable(True)self.tabs.setMovable(True)self.tabs.tabCloseRequested.connect(self.close_current_tab)self.tabs.currentChanged.connect(lambda i: self.setWindowTitle(self.tabs.tabText(i) + " - " + self.name))if mode == "main":self.init_tab = BrowserTab(self)self.init_tab.browser.load(QUrl("https://cn.bing.com"))self.add_new_tab(self.init_tab)def add_blank_tab(self):blank_tab = BrowserTab(self)self.tabs.parent().add_new_tab(blank_tab)def add_new_tab(self, tab):i = self.tabs.addTab(tab, "")self.tabs.setCurrentIndex(i)self.tabs.setTabIcon(i,QIcon('Assets/main.png'))tab.back_button.triggered.connect(tab.browser.back)tab.next_button.triggered.connect(tab.browser.forward)tab.stop_button.triggered.connect(tab.browser.stop)tab.refresh_button.triggered.connect(tab.browser.reload)tab.home_button.triggered.connect(tab.navigate_to_home)tab.enter_button.triggered.connect(tab.navigate_to_url)tab.add_button.triggered.connect(self.add_blank_tab)tab.url_text_bar.returnPressed.connect(tab.navigate_to_url)tab.browser.urlChanged.connect(tab.renew_urlbar)tab.browser.loadProgress.connect(tab.renew_progress_bar)tab.browser.titleChanged.connect(lambda title: (self.tabs.setTabText(i, title) if len(title) <= 11 else self.tabs.setTabText(i, title[:9]+"..."),self.tabs.setTabToolTip(i, title),self.setWindowTitle(self.tabs.tabText(i) + " - " + self.name)))#tab.browser.iconChanged.connect(self.tabs.setTabIcon(i, tab.browser.icon()))def close_current_tab(self, i):if self.tabs.count() > 1:self.tabs.removeTab(i)else:self.close()def resizeEvent(self, event):self.tabs.setGeometry(0, 0, self.width(), self.height())
__init__.py
python">import sys
from PySide6.QtWidgets import QApplication
from main import BrowserWindowif __name__ == '__main__':app = QApplication(sys.argv)MainWindow = BrowserWindow()MainWindow.show()sys.exit(app.exec())