How to resize rows in a QTreeView and a QStandardItemModel? - resize

I'd like to set my rows to a fixed height. I've found an example using QAbstractItemModel, but I'm using QStandardItemModel. When I run the app, the QTreeView is blank. Any thoughts on how I could get this working for a QStandardItemModel?
import sys
from PySide import QtCore, QtGui
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.data = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super(TreeModel, self).__init__(parent)
self.rootItem = TreeItem(None)
for i, c in enumerate("abcdefg"):
child = TreeItem([i, c], self.rootItem)
self.rootItem.appendChild(child)
parent = self.rootItem.childItems[1]
child = TreeItem(["down", "down"], parent)
parent.appendChild(child)
def columnCount(self, parent):
return 2
def data(self, index, role):
if not index.isValid():
return None
if role == QtCore.Qt.DisplayRole:
item = index.internalPointer()
return item.data[index.column()]
elif role == QtCore.Qt.SizeHintRole:
print "giving size hint"
return QtCore.QSize(10, 10)
return None
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return ["A", "B"][section]
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.childItems[row]
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
parentItem = index.internalPointer().parentItem
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return len(parentItem.childItems)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
model = TreeModel()
view = QtGui.QTreeView()
view.setModel(model)
view.setWindowTitle("Simple Tree Model")
view.show()
sys.exit(app.exec_())
Also, I've been looking around for the meaning of the term delegate in Qt, but the concept still isn't clicking in my head yet. Any insight into that would be appreciated as well!

You generally do this using QItemDelegates (or QStyledItemDelegate if you want stylesheet styling to work) by overriding the sizeHint method and always returning a size of a fixed height.
class MyDelegate(QtGui.QStyledItemDelegate):
def sizeHint(self, option, index):
my_fixed_height = 30
size = super(MyDelegate, self).sizeHint(option, index)
size.setHeight(my_fixed_height)
return size
view = QtGui.QTreeView()
delegate = MyDelegate()
view.setItemDelegate(delegate)

Related

Why does QThread freezes Gui Thread while working?

I need to keep updating the table to have up-to-date information. To do this, I created a Thread and implemented an infinite loop in it, which iterates through all the values and compares them, but for some reason the work of this thread affects GUI and as a result, the thread with GUI freezes.
import random
import sys
import pandas as pd
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QObject, pyqtSignal, Qt, QThread, QModelIndex
from PyQt5.QtWidgets import QApplication, QMainWindow
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, header: list):
super(TableModel, self).__init__()
self._data = pd.DataFrame(columns=header)
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return self._data.values[index.row()][index.column()]
return None
def rowCount(self, parent: QModelIndex = ...) -> int:
return len(self._data.values)
def columnCount(self, index):
return self._data.columns.size
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
def setData(self, index, value, role):
if not index.isValid():
return False
row = index.row()
if row < 0 or row >= len(self._data.values):
return False
column = index.column()
if column < 0 or column >= self._data.columns.size:
return False
self._data.iloc[row, column] = value
self.dataChanged.emit(index, index)
return True
def removeRows(self, position, rows, parent=QModelIndex()):
start, end = position, position + rows - 1
if 0 <= start <= end < self.rowCount(parent):
self.beginRemoveRows(parent, start, end)
for index in range(start, end + 1):
self._data.drop(index, inplace=True)
self._data.reset_index(drop=True, inplace=True)
self.endRemoveRows()
return True
return False
def insertRows(self, position, rows, parent=QModelIndex()):
start, end = position, position + rows - 1
if 0 <= start <= end:
self.beginInsertRows(parent, start, end)
for index in range(start, end + 1):
default_row = [[None] for _ in range(self._data.shape[1])]
new_df = pd.DataFrame(dict(zip(list(self._data.columns), default_row)))
self._data = pd.concat([self._data, new_df])
self._data = self._data.reset_index(drop=True)
self.endInsertRows()
return True
return False
def addRow(self, item):
self.insertRows(self.rowCount(), 1)
row = self.rowCount() - 1
self.setData(self.index(row, 0), item[0], Qt.DisplayRole)
self.setData(self.index(row, 1), item[1], Qt.DisplayRole)
self.setData(self.index(row, 2), item[2], Qt.DisplayRole)
self.setData(self.index(row, 3), item[3], Qt.DisplayRole)
self.setData(self.index(row, 4), item[4], Qt.DisplayRole)
def getTable(self):
return self._data
class Updater(QObject):
addRow = pyqtSignal(object)
def __init__(self, table: TableModel):
super().__init__()
self.items = []
for i in range(50):
item = [
random.randint(10, 100),
random.randint(10, 100),
random.randint(10, 100),
random.randint(10, 100),
random.randint(10, 100),
]
self.items.append(item)
self._table = table
self._status = False
def start(self):
self._status = True
if self._table.getTable().empty:
items = self.items
for item in items:
self.addRow.emit(item)
while self._status:
self.Update(self.items)
def Update(self, items: list):
# This function is looking for items in table and changes data if its not up-to-date
data = self._table.getTable()
for item in items:
result = data.loc[
(data['Col1'] == item[0]) &
(data['Col2'] == item[1]) &
(data['Col3'] == item[2])
]
if len(result.values) != 0:
pass
# Code ...
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setObjectName("MainWindow")
self.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.tableWidget = QtWidgets.QTableView(self.centralwidget)
self.tableWidget.setObjectName("tableWidget")
self.gridLayout.addWidget(self.tableWidget, 0, 0, 1, 1)
self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
self.setCentralWidget(self.centralwidget)
header = ["Col1", "Col2", "Col3", "Col4", "Col5"]
self.model = TableModel(header)
self.tableWidget.setModel(self.model)
self._updater = Updater(self.model)
self._thread = QThread()
self._updater.moveToThread(self._thread)
self._thread.started.connect(self._updater.start)
self._thread.finished.connect(self._thread.deleteLater)
self._updater.addRow.connect(self.model.addRow, Qt.QueuedConnection)
self.show()
self._thread.start()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
Do not judge strictly. I've only recently started learning pyqt and I don't really understand it yet.Thank you in advance!

How to open context menu for an object that is empty, but has a size?

I am creating a GUI for a dependency graphing software... And am not able to figure out how to get a context menu to open for my lines.
What I want to do, right click on/near a MyLine widget and open a context menu... What is happening right clicks are not detected.
It is currently not detecting right clicks on the line widgets location to open a context menu (Purpose of this is to allow the user to delete/edit lines by right clicking on them).
What am I doing wrong here?
class MyLine(QWidget):
def __init__(self, destination: Node, source: Node, parent=None):
super().__init__(parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showMenu)
self.destination = destination
self.source = source
self.setAutoFillBackground(True)
p = self.palette()
p.setColor(self.backgroundRole(), Qt.red)
self.setPalette(p)
def update_line_size(self):
origin = self.source.get_line_draw_pos(self.destination.pos())
destination = self.destination.get_line_draw_pos(self.source.pos())
leftcornerX = origin.x() if origin.x() < destination.x() else destination.x()
leftcornerY = origin.y() if origin.y() < destination.y() else destination.y()
sizeX = abs(origin.x() - destination.x())
sizeY = abs(origin.y() - destination.y())
self.setGeometry(leftcornerX, leftcornerY, sizeX, sizeY)
def showMenu(self, _):
menu = QMenu()
menu.addAction("Delete", self.remove)
menu.exec_(self.cursor().pos())
def draw(self, painter: QPainter):
origin = self.source.get_line_draw_pos(self.destination.pos())
destination = self.destination.get_line_draw_pos(self.source.pos())
painter.drawLine(origin, destination)
# DRAW ARROW HEAD
ARROW_SIZE = 10 # Might change
line_angle = calculate_line_angle(destination, origin)
draw_arrow_head(destination, painter, line_angle, ARROW_SIZE)
def remove(self):
self.parent().delete_line(self)
self.deleteLater()
Edit:
required types for reproducibility
class Node(QLabel):
def __init__(self, text: str, parent=None):
super().__init__(text, parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showMenu)
def get_line_draw_pos(self, other_side: QPoint):
x = self.pos().x() if other_side.x() < self.pos().x() else (self.pos().x() + self.width())
y = self.pos().y() if other_side.y() < self.pos().y() else (self.pos().y() + self.height())
return QPoint(x, y)
def showMenu(self, _):
pass #purposefully left as a stub
def calculate_line_angle(destination: QPoint, origin: QPoint):
return math.atan2(destination.y() - origin.y(), destination.x() - origin.x())
def draw_arrow_head(destination: QPoint, painter: QPainter, line_angle: float, arrow_size: float = 10):
angle1 = math.radians(22.5) + line_angle
angle2 = math.radians(-22.5) + line_angle
arrow1 = QPoint( int(destination.x() - arrow_size * math.cos(angle1)), int(destination.y() - arrow_size * math.sin(angle1)))
arrow2 = QPoint( int(destination.x() - arrow_size * math.cos(angle2)), int(destination.y() - arrow_size * math.sin(angle2)))
painter.drawLine(destination, arrow1)
painter.drawLine(destination, arrow2)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setAcceptDrops(True) # add a drop rule
self.setMouseTracking(True)
self.track_origin = None
self.track_mouse = QPoint(0,0)
self.lines = []
def paintEvent(self, event):
painter = QPainter(self)
for line in self.lines:
line.draw(painter)
line.update_line_size()
def connectNodes(self, destination: Node, source: Node):
self.lines.append(MyLine(destination, source))
self.update()
def delete_line(self, line: MyLine):
self.lines.remove(line)
self.update()
app = QApplication([])
window = MainWindow()
window.setWindowTitle("Right Click to remove label")
window.setGeometry(100, 100, 400, 200)
window.move(60,15)
nodes = []
for index, node_name in enumerate(["hello.txt", "not_a_villain.txt", "nope.txt"]):
node = Node(node_name, window)
node.move(50 + index*100, 50 + (index%2) * 50)
nodes.append(node)
window.connectNodes(nodes[0], nodes[1])
window.connectNodes(nodes[0], nodes[2])
window.connectNodes(nodes[1], nodes[2])
window.show()
sys.exit(app.exec_())

Try to understand QSignalMapper with custom Object

I am trying to understand the QSignalMapper. I got how to map a button click with the slot that handles str. I was trying to map a QObject to do the same, but it keeps failing. Am I doing something wrong or did I miss understanding something ?
class TObject(QObject):
def __init__(self):
super().__init__(None)
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setLayout(QVBoxLayout())
fruit_list = ["apples", "oranges", "pears"]
sigMapper = QSignalMapper(self)
sigMapper.mapped[str].connect(self.SLOTSTR) # type:ingore
sigMapper.mapped[TObject].connect(self.SLOTOBJECT) # type:ingore
for i, fruit in enumerate(fruit_list):
btn = QPushButton(fruit)
btn.clicked.connect(sigMapper.map)
sigMapper.setMapping(btn, TObject() if i == 0 else str(fruit))
self.layout().addWidget(btn)
def SLOTSTR(self, s: str):
print("SLOTSTR", s)
def SLOTOBJECT(self):
print("SLOTOBJECT")
I just figured out the error.
sigMapper.setMapping(btn, TObject() if i == 0 else str(fruit))
to
sigMapper.setMapping(btn, TObject(self) if i == 0 else str(fruit))

Synchronize two QGraphicsView with different images

I would like to show two images next to each other, such that when I zoom or pan on one image the other image follows along. My current approach is to emit a viewUpdated event after resolving mouse events. The event contains the viewportTransformation and is used to update the transform in the other view. This sort of works for the zoom part, but panning does not work.
The code below is based on the qt5 version provided in this answer: https://stackoverflow.com/a/35514531/185475
from PyQt5 import QtCore, QtGui, QtWidgets
# Code from https://stackoverflow.com/a/35514531
class PhotoViewer(QtWidgets.QGraphicsView):
photoClicked = QtCore.pyqtSignal(QtCore.QPoint)
viewUpdated = QtCore.pyqtSignal(QtGui.QTransform)
def __init__(self, parent):
super(PhotoViewer, self).__init__(parent)
self._zoom = 0
self._empty = True
self._scene = QtWidgets.QGraphicsScene(self)
self._photo = QtWidgets.QGraphicsPixmapItem()
self._scene.addItem(self._photo)
self.setScene(self._scene)
self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))
self.setFrameShape(QtWidgets.QFrame.NoFrame)
def hasPhoto(self):
return not self._empty
def fitInView(self, scale=True):
rect = QtCore.QRectF(self._photo.pixmap().rect())
if not rect.isNull():
self.setSceneRect(rect)
if self.hasPhoto():
unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
self.scale(1 / unity.width(), 1 / unity.height())
viewrect = self.viewport().rect()
scenerect = self.transform().mapRect(rect)
factor = min(viewrect.width() / scenerect.width(),
viewrect.height() / scenerect.height())
self.scale(factor, factor)
self._zoom = 0
def setPhoto(self, pixmap=None):
self._zoom = 0
if pixmap and not pixmap.isNull():
self._empty = False
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
self._photo.setPixmap(pixmap)
else:
self._empty = True
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
self._photo.setPixmap(QtGui.QPixmap())
self.fitInView()
def wheelEvent(self, event):
if self.hasPhoto():
if event.angleDelta().y() > 0:
factor = 1.25
self._zoom += 1
else:
factor = 0.8
self._zoom -= 1
if self._zoom > 0:
self.scale(factor, factor)
elif self._zoom == 0:
self.fitInView()
else:
self._zoom = 0
self.viewUpdated.emit(self.viewportTransform())
def toggleDragMode(self):
if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag:
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
elif not self._photo.pixmap().isNull():
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
def mousePressEvent(self, event):
if self._photo.isUnderMouse():
self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())
super(PhotoViewer, self).mousePressEvent(event)
self.viewUpdated.emit(self.viewportTransform())
def set_transform(self, transform):
self.setTransform(transform)
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.viewer = PhotoViewer(self)
self.viewerSecondImage = PhotoViewer(self)
self.viewer.viewUpdated.connect(self.viewerSecondImage.set_transform)
self.viewerSecondImage.viewUpdated.connect(self.viewer.set_transform)
# 'Load image' button
self.btnLoad = QtWidgets.QToolButton(self)
self.btnLoad.setText('Load image')
self.btnLoad.clicked.connect(self.loadImage)
# Button to change from drag/pan to getting pixel info
self.btnPixInfo = QtWidgets.QToolButton(self)
self.btnPixInfo.setText('Enter pixel info mode')
self.btnPixInfo.clicked.connect(self.pixInfo)
self.editPixInfo = QtWidgets.QLineEdit(self)
self.editPixInfo.setReadOnly(True)
self.viewer.photoClicked.connect(self.photoClicked)
# Arrange layout
VBlayout = QtWidgets.QVBoxLayout(self)
HBlayoutImageViewers = QtWidgets.QHBoxLayout()
HBlayoutImageViewers.addWidget(self.viewer)
HBlayoutImageViewers.addWidget(self.viewerSecondImage)
VBlayout.addLayout(HBlayoutImageViewers)
HBlayout = QtWidgets.QHBoxLayout()
HBlayout.setAlignment(QtCore.Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)
HBlayout.addWidget(self.btnPixInfo)
HBlayout.addWidget(self.editPixInfo)
VBlayout.addLayout(HBlayout)
def loadImage(self):
self.viewer.setPhoto(QtGui.QPixmap('input/490px-Dostojka_adype.jpg'))
self.viewerSecondImage.setPhoto(QtGui.QPixmap('input/490px-Dostojka_adype.jpg'))
def pixInfo(self):
self.viewer.toggleDragMode()
def photoClicked(self, pos):
if self.viewer.dragMode() == QtWidgets.QGraphicsView.NoDrag:
self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 800, 600)
window.show()
sys.exit(app.exec_())
You only need to syncronize scrollbar values of two QGraphicsViews
def bindScrollBars(scrollBar1, scrollBar2):
# syncronizing scrollbars syncrnonously somehow breaks zooming and doesn't work
# scrollBar1.valueChanged.connect(lambda value: scrollBar2.setValue(value))
# scrollBar2.valueChanged.connect(lambda value: scrollBar1.setValue(value))
# syncronizing scrollbars asyncronously works ok
scrollBar1.valueChanged.connect(lambda _: QtCore.QTimer.singleShot(0, lambda: scrollBar2.setValue(scrollBar1.value())))
scrollBar2.valueChanged.connect(lambda _: QtCore.QTimer.singleShot(0, lambda: scrollBar1.setValue(scrollBar2.value())))
class Window(QtWidgets.QWidget):
def __init__(self):
...
bindScrollBars(self.viewer.horizontalScrollBar(), self.viewerSecondImage.horizontalScrollBar())
bindScrollBars(self.viewer.verticalScrollBar(), self.viewerSecondImage.verticalScrollBar())
Also wheelEvent can be simplified
def wheelEvent(self, event):
if self.hasPhoto():
factor = 1.25
if event.angleDelta().y() > 0:
self.scale(factor, factor)
else:
self.scale(1/factor, 1/factor)
self.viewUpdated.emit(self.transform())

PyQt5: detect a new inserted tab with QTabWidget::tabInserted(int index)

I'm new in PyQt5. I made a tabWidget where I was able to connect a button to add tabs dynamically and remove/close them. Before inserting tabs with button.Clicked, there's one open (already inserted) tab saying "no tabs are open". I would like to close this tab after inserting a new tab and reopen the tab after closing all new tabs (when no tabs are open). I couldn't find any example on how to use QTabWidget::tabInserted(int index) from http://doc.qt.io/qt-5/qtabwidget.html. Does anyone know how to use tabInserted and tabRemoved to detect when triggered. I would like to increment each time a tab is inserted and decrement when removed, to know when there are open tabs and no open tabs. Thanks
self.toolButton.clicked.connect(self.button_addtab)
self.tabWidget.tabCloseRequested.connect(self.close_tab)
def button_addtab(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.tabWidget.addTab(self.tab_2, "")
self.tabWidget.setCurrentIndex(pages-0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Second_tab"))
def close_tab(self, index):
self.tabWidget.removeTab(index)
I tried this:
if self.tabWidget.count() <= 0:
#Add the "no tab open" tab
self.tab_3 = QtWidgets.QWidget()
self.tab_3.setObjectName("tab_3")
self.tabWidget.addTab(self.tab_3, "")
_translate = QtCore.QCoreApplication.translate
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Info"))
elif self.tabWidget.count() > 0:
self.tabWidget.removeTab(self.tabWidget.indexOf(self.tab_3))
This work, but it doesn't add after closing and it reopens after one more tab is added. That's why I would like to use tabInserted
See class TabWidget
import sys
from PyQt5.QtCore import Qt, QRect
from PyQt5.QtGui import QColor, QPainter
from PyQt5.QtWidgets import (QApplication, QWidget, QMainWindow, QAction,
QVBoxLayout, QTabWidget, QFileDialog, QPlainTextEdit, QHBoxLayout)
lineBarColor = QColor(53, 53, 53)
lineHighlightColor = QColor('#00FF04')
class TabWidget(QTabWidget):
def __init__(self, parent=None):
super(TabWidget, self).__init__(parent)
# This virtual handler is called after a tab was removed from position index.
def tabRemoved(self, index):
print("\n tab was removed from position index -> {}".format(index))
# This virtual handler is called after a new tab was added or inserted at position index.
def tabInserted(self, index):
print("\n New tab was added or inserted at position index -> {}".format(index))
class NumberBar(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.editor = parent
layout = QVBoxLayout(self)
self.editor.blockCountChanged.connect(self.update_width)
self.editor.updateRequest.connect(self.update_on_scroll)
self.update_width('001')
def mousePressEvent(self, QMouseEvent):
print("\n - class NumberBar(QWidget): \n\tdef mousePressEvent(self, QMouseEvent):")
def update_on_scroll(self, rect, scroll):
if self.isVisible():
if scroll:
self.scroll(0, scroll)
else:
self.update()
def update_width(self, string):
width = self.fontMetrics().width(str(string)) + 10
if self.width() != width:
self.setFixedWidth(width)
def paintEvent(self, event):
if self.isVisible():
block = self.editor.firstVisibleBlock()
height = self.fontMetrics().height()
number = block.blockNumber()
painter = QPainter(self)
painter.fillRect(event.rect(), lineBarColor)
painter.setPen(Qt.white)
painter.drawRect(0, 0, event.rect().width() - 1, event.rect().height() - 1)
font = painter.font()
current_block = self.editor.textCursor().block().blockNumber() + 1
while block.isValid():
block_geometry = self.editor.blockBoundingGeometry(block)
offset = self.editor.contentOffset()
block_top = block_geometry.translated(offset).top()
number += 1
rect = QRect(0, block_top, self.width() - 5, height)
if number == current_block:
font.setBold(True)
else:
font.setBold(False)
painter.setFont(font)
painter.drawText(rect, Qt.AlignRight, '%i' % number)
if block_top > event.rect().bottom():
break
block = block.next()
painter.end()
class Content(QWidget):
def __init__(self, text):
super(Content, self).__init__()
self.editor = QPlainTextEdit()
self.editor.setPlainText(text)
# Create a layout for the line numbers
self.hbox = QHBoxLayout(self)
self.numbers = NumberBar(self.editor)
self.hbox.addWidget(self.numbers)
self.hbox.addWidget(self.editor)
class MyTableWidget(QWidget):
def __init__(self, parent=None):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Initialize tab screen
self.tabs = TabWidget() #QTabWidget()
self.tabs.resize(300, 200)
# Add tabs
self.tabs.setTabsClosable(True)
self.tabs.tabCloseRequested.connect(self.closeTab)
# Add tabs to widget
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
def closeTab(self, index):
tab = self.tabs.widget(index)
tab.deleteLater()
self.tabs.removeTab(index)
def addtab(self, content, fileName):
self.tabs.addTab(Content(str(content)), str(fileName))
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.open()
self.tabs = MyTableWidget()
self.setCentralWidget(self.tabs)
self.initUI()
self.show()
def initUI(self):
self.statusBar()
menu = self.menuBar()
fileMenu = menu.addMenu('File')
fileMenu.addAction(self.openAct)
self.resize(800, 600)
def closeTab(self, index):
tab = self.tabs.widget(index)
tab.deleteLater()
self.tabs.removeTab(index)
def buttonClicked(self):
self.tabs.addTab(Content("smalltext2"), "sadsad")
def open(self):
self.openAct = QAction('Open...', self)
self.openAct.setShortcut('Ctrl+O')
self.openAct.setStatusTip('Open a file')
self.is_opened = False
self.openAct.triggered.connect(self.openFile)
def openFile(self):
options = QFileDialog.Options()
filenames, _ = QFileDialog.getOpenFileNames(
self, 'Open a file', '',
'Python Files (*.py);;Text Files (*.txt)',
options=options
)
if filenames:
for filename in filenames:
with open(filename, 'r+') as file_o:
try:
text = file_o.read()
self.tabs.addtab(text, filename)
except Exception as e:
print("Error: filename=`{}`, `{}` ".format( filename, str(e)))
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Main()
sys.exit(app.exec_())