Issue
I am trying to drag a tab from a tab widget and split the viewing area (- like eclipse does then you drag a tab heading into the edit area - and similar to the 'split vertically' functionality on a tab in PyCharm).
I tried using drag and drop but can't get the tab main area (eg text area) to register a drag coming from the QTabBar. I then tried just following the mouse movements and got further but the splitter code is not quite working and it's pretty ugly code.
NB I'm not using dockable widgets as this code will be used for the central widget of an app. I'd prefer to use drag and drop to do this - anyone have any ideas?
from PyQt4.QtGui import QWidget, QDrag, QTabBar, QTabWidget, QPainter, QPalette,\
QBrush, QColor, QPen, QVBoxLayout, QHBoxLayout, QTextEdit, QCheckBox
from PyQt4.QtCore import QByteArray, QMimeData, QPoint
from PyQt4 import QtCore, QtGui
class CentralTabWidget(QTabWidget):
def __init__(self, parent=None):
QTabWidget.__init__(self, parent)
self.parent = parent
tabBar = CentralTabBar(self)
self.setTabBar(tabBar)
self.addWidgets()
def addWidgets(self):
tab1 = QWidget()
self.addTab(tab1, "Tab1")
textArea = QTextEdit()
textArea.setText("Text area 1")
vBox = QVBoxLayout()
vBox.addWidget(textArea)
tab1.setLayout(vBox)
tab2 = QWidget()
self.addTab(tab2, "Tab2")
textArea2 = QTextEdit()
textArea2.setText("Text area 2")
vBox2 = QVBoxLayout()
vBox2.addWidget(textArea2)
tab2.setLayout(vBox2)
self.setAcceptDrops(True)
self.verticalLineOverlay = Overlay(parent = self)
self.verticalLineOverlay.hide()
def dragEnterEvent(self, event):
mimeData = event.mimeData()
event.accept()
def dragMoveEvent(self, event):
print(">>dragMoveEvent()")
def dropEvent(self, event):
mimeData = event.mimeData()
event.setDropAction(QtCore.Qt.MoveAction)
event.accept()
def resizeEvent(self, event):
self.verticalLineOverlay.resize(event.size())
event.accept()
class CentralTabBar(QTabBar):
def __init__(self, parent=None):
QTabBar.__init__(self, parent)
self.parent = parent
self.__drag_start_pos = QPoint()
self.setAcceptDrops(True)
def mousePressEvent(self, event):
self.__mousePressPos = None
self.__mouseMovePos = None
if event.button() == QtCore.Qt.LeftButton:
self.__mousePressPos = event.globalPos()
self.__mouseMovePos = event.globalPos()
if self.parent.parent.dndCheckBox.isChecked():
self.startDrag()
super(CentralTabBar, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
self.parent.verticalLineOverlay.setVisible(False)
currPos = self.mapToGlobal(self.pos())
ax, ay, aw, ah = self.geometry().getRect()
if currPos.x() > ax + aw/4:
self.parent.parent.createSplitter()
super(CentralTabBar, self).mouseReleaseEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
currPos = self.mapToGlobal(self.pos())
globalPos = event.globalPos()
diff = globalPos - self.__mouseMovePos
newPos = self.mapFromGlobal(currPos + diff)
xp1, yp1, xp2, yp2 = self.geometry().getCoords()
ax, ay, aw, ah = self.geometry().getRect()
parentx, parenty, parentw, parenth = self.parent.geometry().getCoords()
if parenth > 10:
if (newPos.y() > yp2) and (newPos.y() < (parenth)):
self.parent.verticalLineOverlay.setVisible(True)
super(CentralTabBar, self).mouseMoveEvent(event)
def startDrag(self):
data = QByteArray()
mimeData = QMimeData()
mimeData.setData("application/x-icon-and-text", data)
print("Using DnD")
drag = QDrag(self)
drag.setMimeData(mimeData)
drag.exec_()
def dragEnterEvent(self, event):
event.accept()
def dragMoveEvent(self, event):
print("--dragMoveEvent()")
def dropEvent(self, event):
event.setDropAction(QtCore.Qt.MoveAction)
event.accept()
class Overlay(QWidget):
def __init__(self, parent=None):
super(Overlay, self).__init__(parent)
print("--__init__() parent type:{0}".format(type(parent)))
self.parent = parent
palette = QPalette(self.palette())
palette.setColor(palette.Background, QtCore.Qt.transparent)
self.setPalette(palette)
def paintEvent(self, event):
self.painter = QPainter()
self.painter.setPen(QPen(QtCore.Qt.NoPen))
self.painter.begin(self)
self.painter.setRenderHint(QPainter.Antialiasing)
self.painter.fillRect(self.parent.currentWidget().geometry(), QBrush(QColor(255, 255, 255, 10)))
parentx, parenty, parentw, parenth = self.parent.geometry().getCoords()
self.painter.drawRect( parentx, parenty, (parentw/2)-10, parenth)
self.painter.drawRect( (parentw/2)+10, parenty, parentw, parenth)
self.painter.setPen(QPen(QtCore.Qt.NoPen))
def setUpPainter(self):
self.painter = QPainter()
self.painter.setPen(QPen(QtCore.Qt.NoPen))
self.painter.begin(self)
self.painter.setRenderHint(QPainter.Antialiasing)
self.painter.fillRect(self.parent.currentWidget().geometry(), QBrush(QColor(255, 255, 255, 10)))
parentx, parenty, parentw, parenth = self.parent.geometry().getCoords()
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.layout = QHBoxLayout()
self.dndCheckBox = QCheckBox("Use DnD")
self.dndCheckBox.setChecked(True)
self.ctw = CentralTabWidget(self)
self.layout.addWidget(self.ctw)
self.layout.addWidget(self.dndCheckBox)
self.setLayout(self.layout)
def createSplitter(self):
#not sure why widgets are not redrawn inside the splitter
splitter1 = QtGui.QSplitter(QtCore.Qt.Horizontal)
self.removeWidgets(self.layout)
splitter1.addWidget(self.ctw)
self.layout.addWidget(splitter1)
self.layout.addWidget(self.dndCheckBox)
def removeWidgets(self, layout):
for cnt in reversed(range(layout.count())):
widget = layout.takeAt(cnt).widget()
if widget is not None:
widget.deleteLater()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(350, 300)
window.show()
sys.exit(app.exec_())
Using a QMainWindow inside a QMainMaindow I tried:
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QMainWindow, QTextEdit, QDockWidget
_DOCK_OPTS = QtGui.QMainWindow.AllowNestedDocks
_DOCK_OPTS |= QtGui.QMainWindow.AllowTabbedDocks
class Window(QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
secondQMainWindow = QMainWindow()
self.central = secondQMainWindow
self.setDockOptions(_DOCK_OPTS)
dw1 = QDockWidget("One")
textArea = QTextEdit()
textArea.setText("Text area 1")
dw1.setWidget(textArea)
dw2 = QDockWidget("Two")
textArea2 = QTextEdit()
textArea2.setText("Text area 2")
dw2.setWidget(textArea2)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, dw1)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dw2)
self.tabifyDockWidget(dw1, dw2)
dw3 = QDockWidget("Three")
textArea3 = QTextEdit()
textArea3.setText("Text area 3")
dw3.setWidget(textArea3)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, dw3)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
But I don't think I have the addition of widgets to the parent vs child central widget correct.
Solution
Here is a trick I've used in the past to do what you are looking for. It may be quite limited if you want to have a lot of control on the parameters but it does the job.
- Put a 2nd
QMainWindow
as thecentralWidget
of youQMainWindow
. - Don't set any
centralWidget
for this 2ndQMainWindow
. Set flags :
QMainWindow.AllowNestedDocks
andQMainWindow.AllowTabbedDocks
Add tabs by adding
QDockWidgets
. These will be automatically threated asQTabWidgets
.- Use
QMainWindow.tabifyDockWidget(firstWidget,secondWidget)
to stack the widgets programmatically (it can be used multiple times for more than 2 widgets)
Example :
window=QtGui.QMainWindow()
window.centralContent=QtGui.QMainWindow()
window.setCentralWidget(window.centralContent)
window.centralContent.firstTabWidget=QtGui.QWidget()
window.centralContent.firstTabDock=QtGui.QDockWidget("first")
window.centralContent.firstTabDock.setWidget(window.centralContent.firstTabWidget)
window.centralContent.addDockWidget(window.centralContent.firstTabDock)
window.centralContent.secondTabWidget=QtGui.QWidget()
window.centralContent.secondTabDock=QtGui.QDockWidget("second")
window.centralContent.secondTabDock.setWidget(window.centralContent.secondTabWidget)
window.centralContent.addDockWidget(window.centralContent.secondTabDock)
window.centralContent.tabifyDockWidget( window.centralContent.firstTabDock, window.centralContent.secondTabDock)
Answered By - Olivier Giniaux
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.