wx - how to create two notebooks side by side with different sizes? - wxwidgets

I want to create a window with two notebooks. The left one should be always as narrow as possible. The right one should be as wide as possible and expand when the window is resized. Is it doable in Wx?
This is what I managed to get done in wxFormBuilder. Both notebooks always have the same widths when I resize the window.
I tried changing the sizerItem proportion of each of them, but that obviously only changes the proportion. I want to only allow the right one to expand. Changing the wxEXPAND flag of each notebook only changes the vertical expansion and not horizontal.
Thanks for help.
Generated code:
# -*- coding: utf-8 -*-
###########################################################################
## Python code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
## http://www.wxformbuilder.org/
##
## PLEASE DO *NOT* EDIT THIS FILE!
###########################################################################
import wx
import wx.xrc
###########################################################################
## Class MyFrame1
###########################################################################
class MyFrame1 ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
bSizer1 = wx.BoxSizer( wx.HORIZONTAL )
self.m_notebook1 = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_panel1 = wx.Panel( self.m_notebook1, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
self.m_notebook1.AddPage( self.m_panel1, u"a page", False )
self.m_panel2 = wx.Panel( self.m_notebook1, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
self.m_notebook1.AddPage( self.m_panel2, u"a page", False )
bSizer1.Add( self.m_notebook1, 1, wx.EXPAND |wx.ALL, 5 )
self.m_notebook2 = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_panel3 = wx.Panel( self.m_notebook2, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
self.m_notebook2.AddPage( self.m_panel3, u"a page", False )
self.m_panel4 = wx.Panel( self.m_notebook2, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
self.m_notebook2.AddPage( self.m_panel4, u"a page", False )
bSizer1.Add( self.m_notebook2, 1, wx.EXPAND |wx.ALL, 5 )
self.SetSizer( bSizer1 )
self.Layout()
self.Centre( wx.BOTH )
def __del__( self ):
pass

You just need to set the proportion of the left item to 0 (which is the default) and of the right item to 1, which will allow it to expand to fill all the available space. It's as simple as this and it doesn't matter at all that the windows are notebooks -- the rules are the same for all windows.

Related

issue with update shown values in a pyqt5-applet (using qtableview)

I want to make a qtableview widget correctly updating. I'm working on a calibration applet, where i wanna fill cell by cell of an (e. g.) 100 x 100 x 4 array.
If my hardware reaches position 1, 2, 3, and so on, I will trigger a voltage measurement and gather those values with an i2c-read out-function.
So issues a la "my qtableview is not updating" are omnipresent.
But so far, I'm not able to adapt examples I have read, to make my code behaving as I want.
So if you look at my screenshot:
the problem is:
when I'm clicking on row or col +/-, the yellow highlighting is not changing instantly
when I'm clicking on store i²c, which is meant to put a dummy 0.0 in/on selected cell, this is also not changing instantly
Several methods like telling the model that data has changed, I was not able to implement correctly so far.
Could some of you help me to add a few lines just to force applet to update correctly?
fillCSV_forum.py:
### libraries:
import sys # to use e. g. exit function
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
import pandas as pd # to use pandas tables
import numpy as np # to use numpy arrays
### user-defined header files / modules:
from uLib_coloredWidget import Color # import user-defined functions..
from rndGen import i2c_read # .. see folder
### initial settings:
# general
np.random.seed(4) # if using np.random, then pseudo random values will occure
### globals:
nRow = 5; nCol = 5; nSht = 4 # table dimensions
rowIdx = colIdx = shtIdx = 0 # aux vars to index array
rndArray = np.random.rand(nSht, nRow, nCol) * 4.3 # auxilliary before integrating i2c
tabNames = ["A4", "A5","A6","A7"] # array (list) with tab names
rowIdx = 1; colIdx = 1 # aux vars to index selected cell
### declarations / definitions:
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if role == Qt.BackgroundRole and index.column() == colIdx and index.row() == rowIdx:
# See below for the data structure.
return QtGui.QColor('yellow')
if role == Qt.DisplayRole:
value = self._data.iloc[index.row(), index.column()]
if isinstance(value, float): # to set fixed DISPLAYED precision of floats
return "%.4f" % value
return str(value)
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return self._data.shape[1]
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])
class App(QtWidgets.QMainWindow):
# local variable's declarations
# init
def __init__(self):
super().__init__() # default one
self.setup_main_window() # using helper function to increase readability (function call within self scope)
# setup main window
self.createLayout() # function call to create layouts with widgets
self.post_main_window() # pass edited layouts to main window
# declaration / methods / helper functions
def setup_main_window(self): # to set window's / applet's properties
self.centralwidget = QtWidgets.QWidget()
self.setCentralWidget(self.centralwidget)
self.resize( 800, 400 )
self.setWindowTitle( "# disposition calibration #" )
def post_main_window(self): # to publish edited layouts in app window
self.centralwidget.setLayout(self.lyoOut)
def createLayout(self): # to create layouts with widgets
self.lyoOut = QtWidgets.QVBoxLayout() # declare different layouts
self.lyoIn1 = QtWidgets.QHBoxLayout()
self.lyoIn2 = QtWidgets.QGridLayout()
self.createWidgets() # function call pass widgets to sub-layouts
self.lyoOut.addLayout(self.lyoIn1) # inner layouts got widgets by self.createWidgets()
self.lyoOut.addLayout(self.lyoIn2) # merge edited inner layout in/to outside layout here
def createWidgets(self): # create master-layout's widgets (function calls)
# fill 1st row of ouside layout
self.lyoIn1 = self.createNestedTabs(self.lyoIn1) # function call to create master-tabs
# fill 2nd row of outside layout
self.lyoIn2 = self.createButtons(self.lyoIn2) # function call to create buttons
def createNestedTabs(self, layout2modify): # create 1st tab layer
self.MstTabs = QtWidgets.QTabWidget() # create tabs-widget
self.MstTabs.setTabPosition(QtWidgets.QTabWidget.North) # set it's location
self.MstTabs.addTab(self.createChildTabs(), "data") # add several sub-tab layouts to that widget
self.MstTabs.addTab(Color("orange"), "plot") #
stylesheet = """
QTabBar::tab:selected {background: lightgreen;}
QTabBar::tab:!selected {background: lightyellow;}
"""
self.MstTabs.setStyleSheet(stylesheet)
layout2modify.addWidget(self.MstTabs) # add this tabs-widget to passed-in layout
return layout2modify # return edited layout
def createChildTabs(self): # create 2nd tab layer
self.ChdTabs = QtWidgets.QTabWidget() # create tabs-widget
self.ChdTabs.setTabPosition(QtWidgets.QTabWidget.West) # set it's location
self.ChdTabs.addTab(self.createPandasTables(0), "A4")
self.ChdTabs.addTab(self.createPandasTables(1), "A5")
self.ChdTabs.addTab(self.createPandasTables(2), "A6")
self.ChdTabs.addTab(self.createPandasTables(3), "A7")
return self.ChdTabs # return created widgets
def createPandasTables(self, shtIdx): # to creating and editing pandas tables-widgets
# use indexed (pandas)dataframe sheet values
Lbl = ["a","b","c","d","e"]
self.df = pd.DataFrame(rndArray[shtIdx], columns = Lbl, index = Lbl)
# .. to create a widget
self.table_widget = QtWidgets.QTableView() # create QTableView-Widget
self.model = TableModel(self.df) # make df to user defined table model to use in widgets
self.table_widget.setModel(self.model) # pass created model to created widget
# certain formatings
self.table_widget.resizeColumnsToContents() # set column width to content
self.table_widget.horizontalHeader().setStretchLastSection(True) # strech last column to frame width
self.table_widget.verticalHeader().setStretchLastSection(True) # strech last row to frame height
self.table_widget.setAlternatingRowColors(True) # switch on alternating row highlighting
return self.table_widget # return created widgets
def createButtons(self, layout2modify): # helper function - to create layout's buttons
bStoreI2C = QtWidgets.QPushButton("Store i²c")
bStoreI2C.clicked.connect(lambda:self.storeVal())
bStoreI2C.setStyleSheet("QPushButton::hover"
"{"
"background-color : yellow;"
"}")
layout2modify.addWidget(bStoreI2C, 1, 3, 2, 1)
self.lbl_1 = QtWidgets.QLabel()
self.lbl_1.setText(str(rowIdx))
self.lbl_1.setAlignment(QtCore.Qt.AlignCenter)
layout2modify.addWidget(self.lbl_1, 1, 5, 2, 1)
bRowAdd = QtWidgets.QPushButton("row +")
bRowAdd.clicked.connect(lambda:self.rowAdd())
layout2modify.addWidget(bRowAdd, 2, 6)
bRowSub = QtWidgets.QPushButton("row -")
bRowSub.clicked.connect(lambda:self.rowSub())
layout2modify.addWidget(bRowSub, 1, 6)
return layout2modify # return edited layout
def storeVal(self):
#i2c_vals = get_i2c_values(i2c_addrs)
for i in range (0,4):
#self.tbData[i, rowIdx, colIdx] = i2c_vals[i] # change cell entries with imported value
rndArray[i, rowIdx, colIdx] = 0
#self.tbData[sht, row, col] = 99 # change cell entry with imported value
# try 1
#self.table_widget.update()
#self.table_widget.repaint()
#self.model.select()
#self.table_widget.select()
# try 2
# self.refreshModel() # not working so far
#self.model = TableModel(self.df) # make df to user defined table model to use in widgets
#self.table_widget.setModel(self.model)
# print(rndArray)
print('i²c-value(s) stored')
def rowAdd(self):
global rowIdx
rowIdx = (rowIdx + 1) % nRow # increment and modulo to cycle
self.lbl_1.setText(str(rowIdx)) # update label's text
print('row is ', rowIdx)
def rowSub(self):
global rowIdx
rowIdx = (rowIdx - 1) % nRow # increment and modulo to cycle
self.lbl_1.setText(str(rowIdx)) # update label's text
print('row is ', rowIdx)
### main:
def main():
app = QtWidgets.QApplication(sys.argv) # instanciate app
window = App() # instanciate window
window.show() # show window
app.exec_() # stuck here 'til window is closed
print('# window will be terminated.. #')
time.sleep(2)
print('# ..app execution closed #')
# make file executable
if __name__ == '__main__':
main()
rndGen.py: (is called in fillCSV_forum.py)
import numpy as np
def i2c_read():
floats = np.random.rand(4,1,1) * 4.3
return floats
uLib_coloredWidget.py: (is called in fillCSV_forum.py)
from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtWidgets import QWidget
class Color(QWidget):
def __init__(self, color):
super().__init__()
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QPalette.Window, QColor(color))
self.setPalette(palette)
pip freeze --local-output of virtual enviroment:
numpy==1.23.0
pandas==1.4.3
PyQt5==5.15.7
PyQt5-Qt5==5.15.2
PyQt5-sip==12.11.0
python-dateutil==2.8.2
pytz==2022.1
six==1.16.0
[... additionally many hours of trial and error....]
i think i finally got a dirty solution / work around..
the problem i could determining, was e. g. if i am clicking the col+/- or store button, the focus of recently selected tab is vanishing.
first when click again into any tab region or select another tabs those values are updating.
so i tried to look for programmatically tab swap and did this as a dirty work around because i could not find a method like "reactivate tab again"
i added ... :
def storeVal(self):
#i2c_vals = get_i2c_values(i2c_addrs)
for i in range (0,nSht):
self.df[i].iat[rowIdx, colIdx] = 99
print('i²c-value(s) stored')
self.show_data()
def show_data(self):
x = self.ChdTabs.currentIndex()
print(x) # debugging
self.ChdTabs.setCurrentIndex(1)
self.ChdTabs.setCurrentIndex(x)
... a show method and called it at the end of the store-method.
in this show method i programmatically swap the active tab back and forth. this is so fast, that i cannot see it
now my values are correctly shown
another tiny if else code is necessary to also swap if tab 1 is selected, but this is cosmetic thing

Setting the focuspolicy of differents class of widgets

I am currently learning PyQt concepts. Through easy examples, I am trying to play with the tab key to jump from one widget to another.
I manage to disable the focus policy on a PushButton, making it impossible to get the focus on it while pushing Tab. However, with a DialogButton, I do not manage to do so.
I feel managing this policy is possible with every type of widget, but I feel a bit lost with all their specificities.
Here is my current code :
from PyQt5 import QtWidgets, QtCore, QtGui
import sys
class TabWidget(QtWidgets.QDialog): # class to implement dialog box
def __init__(self):
super().__init__()
self.setWindowTitle("Tab Widget App")
tabWidget = QtWidgets.QTabWidget() # creation of the tab widget object
tabWidget.addTab(FirstTab(), "First Tab") # fill with first widget
tabWidget.addTab(SecondTab(), "Second Tab")
tabWidget.addTab(ThirdTab(), "Third Tab")
self.buttonbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
self.buttonbox.setFocusPolicy(QtCore.Qt.NoFocus)
self.buttonbox.accepted.connect(self.accept)
self.buttonbox.rejected.connect(self.reject)
tabWidget.addTab(FourthTab(), "Fourth Tab")
vbox_layout = QtWidgets.QVBoxLayout()
vbox_layout.addWidget(tabWidget)
vbox_layout.addWidget(self.buttonbox)
self.setLayout(vbox_layout)
def keyPressEvent(self, event):
print(event.key())
class FirstTab(QtWidgets.QWidget): # class to implement simple widget
def __init__(self):
super().__init__()
name = QtWidgets.QLabel("Name:")
nameEdit = QtWidgets.QLineEdit()
dob = QtWidgets.QLabel("Birth Date:")
dobEdit = QtWidgets.QLineEdit()
age = QtWidgets.QLabel("Age:")
ageEdit = QtWidgets.QLineEdit()
phone = QtWidgets.QLabel("Phone:")
phoneEdit = QtWidgets.QLineEdit()
vbox_layout = QtWidgets.QVBoxLayout()
vbox_layout.addWidget(name)
vbox_layout.addWidget(nameEdit)
vbox_layout.addWidget(dob)
vbox_layout.addWidget(dobEdit)
vbox_layout.addWidget(age)
vbox_layout.addWidget(ageEdit)
vbox_layout.addWidget(phone)
vbox_layout.addWidget(phoneEdit)
self.setLayout(vbox_layout)
class SecondTab(QtWidgets.QWidget): # class to implement simple widget
def __init__(self):
super().__init__()
selectGroup = QtWidgets.QGroupBox("Select Operating Systems")
combo = QtWidgets.QComboBox()
list = {"Windows", "Mac", "Linux"}
combo.addItems(list)
selectLayout = QtWidgets.QVBoxLayout()
selectLayout.addWidget(combo)
selectGroup.setLayout(selectLayout)
checkGroup = QtWidgets.QGroupBox("Which Operation System Do You Like?")
windows = QtWidgets.QCheckBox("Windows")
mac = QtWidgets.QCheckBox("Mac")
linux = QtWidgets.QCheckBox("Linux")
checklayout = QtWidgets.QVBoxLayout()
checklayout.addWidget(windows)
checklayout.addWidget(mac)
checklayout.addWidget(linux)
checkGroup.setLayout(checklayout)
mainlayout = QtWidgets.QVBoxLayout()
mainlayout.addWidget(selectGroup)
mainlayout.addWidget(checkGroup)
self.setLayout(mainlayout)
class ThirdTab(QtWidgets.QWidget):
def __init__(self):
super().__init__()
label = QtWidgets.QLabel("Terms And Conditions")
listwidget = QtWidgets.QListWidget()
list = []
for i in range(1,20):
list.append("This Is Terms And Conditions")
listwidget.insertItems(0, list)
checkbox = QtWidgets.QCheckBox("Agree The Terms And Condition")
layout = QtWidgets.QVBoxLayout()
layout.addWidget(label)
layout.addWidget(listwidget)
layout.addWidget(checkbox)
self.setLayout(layout)
class FourthTab(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.groupBox = QtWidgets.QGroupBox("Issue", objectName = 'groupBox')
self.grid_layout = QtWidgets.QGridLayout(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
self.widgetOne = QtWidgets.QTextEdit(self.groupBox, tabChangesFocus = True)
self.widgetOne_label = QtWidgets.QLabel("widgetOne")
self.widgetOne.setObjectName("WidgetOne")
self.grid_layout.addWidget(self.widgetOne, 1, 0, 1, 1)
self.grid_layout.addWidget(self.widgetOne_label, 0, 0, 1, 1)
sizePolicy.setHeightForWidth(self.widgetOne.sizePolicy().hasHeightForWidth())
self.grid_layout.addWidget
self.widgetTwo = QtWidgets.QTextEdit(self.groupBox, tabChangesFocus = True)
self.widgetTwo_label = QtWidgets.QLabel("widgetTwo")
self.widgetTwo.setObjectName("widgetTwo")
self.grid_layout.addWidget(self.widgetTwo, 1, 1, 1, 1)
self.grid_layout.addWidget(self.widgetTwo_label, 0, 1, 1, 1)
sizePolicy.setHeightForWidth(self.widgetTwo.sizePolicy().hasHeightForWidth())
self.grid_layout.addWidget
self.widgetThree = QtWidgets.QTextEdit(self.groupBox, tabChangesFocus = True)
self.widgetThree_label = QtWidgets.QLabel("widgetThree")
self.widgetThree.setObjectName("widgetThree")
self.grid_layout.addWidget(self.widgetThree, 4, 0, 1, 1)
self.grid_layout.addWidget(self.widgetThree_label, 3, 0, 1, 1)
sizePolicy.setHeightForWidth(self.widgetThree.sizePolicy().hasHeightForWidth())
self.grid_layout.addWidget
self.widgetFour = QtWidgets.QTextEdit(self.groupBox, tabChangesFocus = True)
self.widgetFour_label = QtWidgets.QLabel("widgetFour")
self.widgetFour.setObjectName("WidgetFour")
self.grid_layout.addWidget(self.widgetFour, 4, 1, 1, 1)
self.grid_layout.addWidget(self.widgetFour_label, 3, 1, 1, 1)
sizePolicy.setHeightForWidth(self.widgetFour.sizePolicy().hasHeightForWidth())
self.grid_layout.addWidget
v_layout = QtWidgets.QVBoxLayout()
v_layout.addWidget(self.groupBox)
self.setLayout(v_layout)
self.setTabOrder(self.widgetOne, self.widgetTwo)
self.setTabOrder(self.widgetTwo, self.widgetThree)
self.setTabOrder(self.widgetThree, self.widgetFour)
self.setTabOrder(self.widgetFour, self.widgetOne)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
tabwidget = TabWidget()
tabwidget.show()
sys.exit(app.exec_())
The involved button is my "buttonbox" of type QDialogButton. I have set the focus policy to NoFocus. But when I launch the script and use the tab key in FourthTab, the focus still goes on OK / Cancel.
Are there some restrictions about this option? Or is there something I am missing?
The focus policy is not propagated to the children.
When you set the focus policy on the button box, its children (the buttons) still have their default policy (which is StrongFocus).
If you want to disable that for all buttons, you need to do it explicitly, for instance:
self.buttonbox.button(self.buttonbox.Ok).setFocusPolicy(QtCore.Qt.NoFocus)
Or, for any button:
for button in self.buttonbox.findChildren(QtWidgets.QAbstractButton):
button.setFocusPolicy(QtCore.Qt.NoFocus)
Note that the keyPressEvent is only received by a parent if none of its children has handled it, so if you're trying to capture the Tab you need to install an event filter on the whole QApplication instead, otherwise it's still possible that some widget will capture and accept it, thus preventing you to receive it.

Pyqt5 synchronization between two tabwidget which are in different window

I want to make new window by double click tabwidget.
and copy tabwidget's (child which is tablewidget) to new window.
and finally, changing item of new window's tablewidget needs to change mainwindow's tablewidget.
would it be possible?
I have seen this, that answer does copy tabwidget to new window
but remove mainwindow tabwidget.
here is I worked so far.
I managed to make new dialog by double click, but other things.. I dont' have any clues. can anyone can help?
#!/usr/bin/python
# -*- coding: utf8 -*-
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class DetachableTabWidget(QTabWidget):
def __init__(self, parent=None):
QTabWidget.__init__(self)
self.tabBar = self.TabBar(self)
self.tabBar.onDetachTabSignal.connect(self.detachTab)
self.setTabBar(self.tabBar)
print("DetachableTabWidget")
#pyqtSlot(int, QPoint)
def detachTab(self, index, point):
print("detachTab")
all_list = []
list1 = []
list2 = []
name = self.tabText(index)
icon = self.tabIcon(index)
if icon.isNull():
icon = self.window().windowIcon()
contentWidget = self.widget(index)
contentWidgetRect = contentWidget.frameGeometry()
tablewidgetA = contentWidget.findChild(QTableWidget)
for i in range(tablewidgetA.rowCount()):
list1.append(tablewidgetA.item(i, 0).text())
list2.append(tablewidgetA.item(i, 1).text())
all_list.append(list1)
all_list.append(list2)
detachedTab = self.DetachedTab(all_list)
detachedTab.setWindowModality(Qt.NonModal)
detachedTab.setWindowTitle(name)
detachedTab.setWindowIcon(icon)
detachedTab.setObjectName(name)
detachedTab.setGeometry(contentWidgetRect)
detachedTab.move(point)
detachedTab.exec_()
class DetachedTab(QDialog) :
onCloseSignal = pyqtSignal(QWidget,type(''), QIcon)
# def __init__(self, contentWidget, parent=None):
def __init__(self, all_list, parent=None) :
print("DetachedTab")
super().__init__()
layout = QVBoxLayout(self)
table = QTableWidget()
table.setColumnCount(len(all_list))
table.setRowCount(len(all_list[0]))
for col in range(len(all_list)) :
for row in range(len(all_list[col])) :
item = QTableWidgetItem(all_list[col][row])
table.setItem(row, col, item)
layout.addWidget(table)
table.show()
class TabBar(QTabBar):
onDetachTabSignal = pyqtSignal(int, QPoint)
onMoveTabSignal = pyqtSignal(int, int)
def __init__(self, parent=None):
QTabBar.__init__(self, parent)
self.setAcceptDrops(True)
self.setElideMode(Qt.ElideRight)
self.setSelectionBehaviorOnRemove(QTabBar.SelectLeftTab)
self.dragStartPos = QPoint()
self.dragDropedPos = QPoint()
self.mouseCursor = QCursor()
self.dragInitiated = False
def mouseDoubleClickEvent(self, event) :
event.accept()
self.onDetachTabSignal.emit(self.tabAt(event.pos()), self.mouseCursor.pos())
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.centralTabs = DetachableTabWidget()
self.setCentralWidget(self.centralTabs)
self.setFixedWidth(600)
self.setFixedHeight(600)
#tab 1
self.tab_1 = QWidget()
self.centralTabs.addTab(self.tab_1,"Tab 1")
vbox = QVBoxLayout()
Table = QTableWidget(2, 2)
vbox.addWidget(Table)
item = QTableWidgetItem("table 1 content")
Table.setItem( 0, 0, item)
item = QTableWidgetItem("table 2 content")
Table.setItem( 0, 1, item)
item = QTableWidgetItem("table 3 content")
Table.setItem( 1, 0, item)
item = QTableWidgetItem("table 4 content")
Table.setItem( 1, 1, item)
vbox.setAlignment(Qt.AlignTop)
self.tab_1.setLayout(vbox)
#tab 2
self.tab_2 = QWidget()
self.centralTabs.addTab(self.tab_2,"Tab 2")
vbox = QVBoxLayout()
Table = QTableWidget(2, 2)
item = QTableWidgetItem("table 2 content")
Table.setItem( 0, 0, item)
item = QTableWidgetItem("table 3 content")
Table.setItem( 0, 1, item)
item = QTableWidgetItem("table 4 content")
Table.setItem( 1, 0, item)
item = QTableWidgetItem("table 5 content")
Table.setItem( 1, 1, item)
vbox.addWidget(Table)
vbox.setAlignment(Qt.AlignTop)
self.tab_2.setLayout(vbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
If you want to synchronize data between item views, you have to use a common model. Since you're using a QTableWidget (which has an internal, private model, and a higher level item view) you can create a new window using a QTableView instead, and set its model to the source. In that case, you don't need to "copy" row/column/data, you only need to use the source model.
Here's a modified version of your script:
class DetachableTabWidget(QTabWidget):
# ...
#pyqtSlot(int, QPoint)
def detachTab(self, index, point):
print("detachTab")
name = self.tabText(index)
icon = self.tabIcon(index)
if icon.isNull():
icon = self.window().windowIcon()
contentWidget = self.widget(index)
contentWidgetRect = contentWidget.frameGeometry()
tablewidgetA = contentWidget.findChild(QTableWidget)
detachedTab = self.DetachedTab(tablewidgetA.model())
detachedTab.setWindowTitle(name)
detachedTab.setWindowIcon(icon)
detachedTab.setObjectName(name)
detachedTab.setGeometry(contentWidgetRect)
detachedTab.move(point)
detachedTab.exec_()
class DetachedTab(QDialog) :
onCloseSignal = pyqtSignal(QWidget,type(''), QIcon)
def __init__(self, model, parent=None) :
print("DetachedTab")
super().__init__()
layout = QVBoxLayout(self)
table = QTableView()
table.setModel(model)
layout.addWidget(table)
table.show()
With this code you can modify the "child" window table data, and it will always synchronize the source table widget.

Own drag icon with same color and font settings as the default drag icon in a Gtk.TreeView

The Gtk.TreeView implements a default drag icon. It use the background color of the TreeView, it's font and the complete row-content as string.
I want the same (background-color, font-face, font-size, font-color) but with a shorter string (only the second of three columns).
In the example below create my own cairo.Surface to create such an icon. But color and font is a problem. I don't know how to set them up or (much more important) to ask the TreeView or Gtk itself for the current color and font values.
How does the TreeView get this values?
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
import cairo
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="TreeView Drag and Drop")
self.connect("delete-event", Gtk.main_quit)
self.box = Gtk.Box()
self.add(self.box)
# "model" with dummy data
self.store = Gtk.TreeStore(int, str, int)
for i in range(5):
self.store.append(None, [i, 'Item {}'.format(i), i]) # treeview
self.tree = Gtk.TreeView(model=self.store)
self.box.pack_start(self.tree, True, True, 0)
# build columns
colA = Gtk.TreeViewColumn('Col A', Gtk.CellRendererText(), text=0)
self.tree.append_column(colA)
colB = Gtk.TreeViewColumn('Col B', Gtk.CellRendererText(), text=1)
self.tree.append_column(colB)
colC = Gtk.TreeViewColumn('Col C', Gtk.CellRendererText(), text=2)
self.tree.append_column(colC)
# enable default drag and drop
self.tree.set_reorderable(True)
# DnD events
self.tree.connect_after("drag-begin", self.drag_begin)
def drag_begin(self, widget, context):
model, path = widget.get_selection().get_selected_rows()
text = model[path][1]
# dummy surface/context
surface = cairo.ImageSurface(cairo.Format.RGB24, 0, 0)
cr = cairo.Context(surface)
# calculate text size
txtext = cr.text_extents(text)
width = int(txtext.width)
height = int(txtext.height)
offset = 10
# creal surface/context
surface = cairo.ImageSurface(cairo.Format.RGB24,
width + (offset*2),
height + (offset*2))
cr = cairo.Context(surface)
cr.set_source_rgb(1, 1, 1) # text color: white
cr.move_to(0+offset, height+offset)
cr.show_text(text)
# use the surface as drag icon
Gtk.drag_set_icon_surface(context, surface)
win = MainWindow()
win.show_all()
Gtk.main()
What I tried (but not worked) was cairo.Surface.create_similar()',cairo.Surface.create_similar_image()andGtk.TreeView.create_row_drag_icon()`.
This answer is based on a foreign mailing list posting.
The widget has a Gtk.StyleContext. A Pango.Layout is used to render the text based on the style informations in the Gtk.StyleContext.
def drag_begin(self, widget, context):
model, path = widget.get_selection().get_selected_rows()
text = model[path][1]
stylecontext = widget.get_style_context()
# new pango layout
pl = widget.create_pango_layout(text)
ink_rec, log_rect = pl.get_pixel_extents()
padding = 5
# create surface/context
surface = cairo.ImageSurface(cairo.Format.RGB24,
log_rect.width + (padding*2),
log_rect.height + (padding*2))
cr = cairo.Context(surface)
Gtk.render_background(stylecontext, cr, 0, 0,
log_rect.width + (padding*2),
log_rect.height + (padding*2))
Gtk.render_layout(stylecontext, cr, padding, padding, pl)
# border
line_width = cr.get_line_width()
cr.rectangle(-1+line_width, -1+line_width,
log_rect.width+(padding*2)-line_width,
log_rect.height+(padding*2)-line_width)
cr.stroke()
# use the surface as drag icon
Gtk.drag_set_icon_surface(context, surface)

PyGtk Serialization

I am currently working on a Note taking app in pyGtk and have set up a TextView where a user can type and add text tags for Bold Underline and Italics.
However, when it comes to saving the formatted text I cannot figure out how to do so.
I am trying to save in Gtk's native tagset format however after using
tag_format = TextBuffer.register_serialize_tagset()
content = TextBuffer.serialize(self, tag_format, start,end)
I cannot write this to a file with
open(filename, 'w').write(content)
because I get an error which states that it cannot write in bytes and needs a string instead.
I am currently working on a Note taking app in pyGtk and have set up a TextView where a user can type and add text tags for Bold Underline and Italics.
However, when it comes to saving the formatted text I cannot figure out how to do so.
I am trying to save in Gtk's native tagset format however after using
tag_format = TextBuffer.register_serialize_tagset()
content = TextBuffer.serialize(self, tag_format, start,end)
I cannot write this to a file with
open(filename, 'w').write(content)
because I get an error which states that it cannot write in bytes and needs a string instead.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango
I am currently working on a Note taking app in pyGtk and have set up a TextView where a user can type and add text tags for Bold Underline and Italics.
However, when it comes to saving the formatted text I cannot figure out how to do so.
I am trying to save in Gtk's native tagset format however after using
tag_format = TextBuffer.register_serialize_tagset()
content = TextBuffer.serialize(self, tag_format, start,end)
I cannot write this to a file with
open(filename, 'w').write(content)
because I get an error which states that it cannot write in bytes and needs a string instead.
File "example.py", line 87, in save_file
open(filename, 'w').write(content)
TypeError: write() argument must be str, not bytes
Here is sample code you can run and test by typing and then saving
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango
class MainWindow(Gtk.ApplicationWindow):
def __init__(self):
Gtk.Window.__init__(self, title = "TwoNote")
self.grid = Gtk.Grid()
self.toolbar = Gtk.Toolbar()
self.grid.add(self.toolbar)
#buttons for toolbar
self.button_bold = Gtk.ToggleToolButton()
self.button_italic = Gtk.ToggleToolButton()
self.button_underline = Gtk.ToggleToolButton()
self.button_save = Gtk.ToolButton()
self.button_open = Gtk.ToolButton()
self.mytext = TextSet(self.button_bold, self.button_italic, self.button_underline)
self.button_bold.set_icon_name("format-text-bold-symbolic")
self.toolbar.insert(self.button_bold, 0)
self.button_italic.set_icon_name("format-text-italic-symbolic")
self.toolbar.insert(self.button_italic, 1)
self.button_underline.set_icon_name("format-text-underline-symbolic")
self.toolbar.insert(self.button_underline, 2)
self.toolbar.insert(self.button_save, 3)
self.toolbar.insert(self.button_open, 4)
self.button_open.set_icon_name("document-open-data")
self.button_save.set_icon_name("document-save")
self.button_save.connect("clicked", self.save_file)
self.button_open.connect("clicked", self.open_file)
self.button_bold.connect("toggled", self.mytext.on_button_clicked, "Bold", self.button_italic, self.button_underline)
self.button_italic.connect("toggled", self.mytext.on_button_clicked, "Italic", self.button_bold, self.button_underline)
self.button_underline.connect("toggled", self.mytext.on_button_clicked, "Underline", self.button_bold, self.button_italic)
self.grid.attach_next_to(self.mytext, self.toolbar, Gtk.PositionType.BOTTOM, 10,30)
self.add(self.grid)
filename = "Untitled"
def open_file(self, widget):
open_dialog = Gtk.FileChooserDialog("Open an existing file", self, Gtk.FileChooserAction.OPEN,(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
open_response = open_dialog.run()
if open_response == Gtk.ResponseType.OK:
filename = open_dialog.get_filename()
text = open(filename).read()
self.mytext.get_buffer().set_text(text)
open_dialog.destroy()
elif open_response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
open_dialog.destroy()
def save_file(self, widget):
savechooser = Gtk.FileChooserDialog('Save File', self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
allfilter = Gtk.FileFilter()
allfilter.set_name('All files')
allfilter.add_pattern('*')
savechooser.add_filter(allfilter)
txtFilter = Gtk.FileFilter()
txtFilter.set_name('Text file')
txtFilter.add_pattern('*.txt')
savechooser.add_filter(txtFilter)
response = savechooser.run()
if response == Gtk.ResponseType.OK:
filename = savechooser.get_filename()
print(filename, 'selected.')
buf = self.mytext.get_buffer()
start, end = buf.get_bounds()
tag_format = buf.register_serialize_tagset()
content = buf.serialize(buf, tag_format, start, end)
try:
open(filename, 'w').write(content)
except SomeError as e:
print('Could not save %s: %s' % (filename, err))
savechooser.destroy()
elif response == Gtk.ResponseType.CANCEL:
print('Closed, file not saved.')
savechooser.destroy()
class TextSet(Gtk.TextView):
def __init__(self, buttonBold, buttonItalic, buttonUnderline, interval = 1 ):
# Textview Setup
Gtk.TextView.__init__(self)
self.set_vexpand(True)
self.set_indent(10)
self.set_top_margin(90)
self.set_left_margin(20)
self.set_right_margin(20)
self.set_wrap_mode(Gtk.WrapMode.CHAR)
self.tb = TextBuffer()
self.set_buffer(self.tb)
# Thread setup
self.button_bold = buttonBold
self.button_italic = buttonItalic
self.button_underline = buttonUnderline
def on_button_clicked(self, widget, tagname, widget1, widget2):
state = widget.get_active()
name = widget.get_icon_name()
bounds = self.tb.get_selection_bounds()
self.tagname = tagname
if(state):
widget1.set_active(False)
widget2.set_active(False)
#highlighting
if(len(bounds) != 0):
start, end = bounds
myIter = self.tb.get_iter_at_mark(self.tb.get_insert())
myTags = myIter.get_tags()
if(myTags == [] and state == True):
self.tb.apply_tag_by_name(tagname, start, end)
elif(myTags != [] and state == True):
self.tb.remove_all_tags(start, end)
self.tb.apply_tag_by_name(tagname, start, end)
else:
for i in range(len(myTags)):
if(myTags[i].props.name == tagname):
self.tb.remove_tag_by_name(tagname,start,end)
myTags = []
self.tb.markup(widget, tagname)
def mouse_clicked(self, window, event):
self.button_bold.set_active(False)
self.button_italic.set_active(False)
self.button_underline.set_active(False)
class TextBuffer(Gtk.TextBuffer):
def __init__(self):
Gtk.TextBuffer.__init__(self)
self.connect_after('insert-text', self.text_inserted)
# A list to hold our active tags
self.taglist_on = []
# Our Bold tag.
self.tag_bold = self.create_tag("Bold", weight=Pango.Weight.BOLD)
self.tag_none = self.create_tag("None", weight=Pango.Weight.NORMAL)
self.tag_italic = self.create_tag("Italic", style=Pango.Style.ITALIC)
self.tag_underline = self.create_tag("Underline", underline=Pango.Underline.SINGLE)
def get_iter_position(self):
return self.get_iter_at_mark(self.get_insert())
def markup(self, widget, tagname):
self.tag_name = tagname
self.check = True
''' add "bold" to our active tags list '''
if(widget.get_active() == True):
if(self.tag_name == 'Bold'):
if 'Bold' in self.taglist_on:
del self.taglist_on[self.taglist_on.index('Bold')]
else:
self.taglist_on.append('Bold')
if(self.tag_name == 'Italic'):
if 'Italic' in self.taglist_on:
del self.taglist_on[self.taglist_on.index('Italic')]
else:
self.taglist_on.append('Italic')
if(self.tag_name == 'Underline'):
if 'Underline' in self.taglist_on:
del self.taglist_on[self.taglist_on.index('Underline')]
else:
self.taglist_on.append('Underline')
else:
self.check = False
def text_inserted(self, buffer, iter, text, length):
# A text was inserted in the buffer. If there are ny tags in self.tags_on, apply them
#if self.taglist_None or self.taglist_Italic or self.taglist_Underline or self.taglist_Bold:
if self.taglist_on:
# This sets the iter back N characters
iter.backward_chars(length)
# And this applies tag from iter to end of buffer
if(self.check == True):
if(self.tag_name == 'Italic'):
self.apply_tag_by_name('Italic', self.get_iter_position(), iter)
if(self.tag_name == 'Bold'):
self.apply_tag_by_name('Bold', self.get_iter_position(), iter)
if(self.tag_name == 'Underline'):
self.apply_tag_by_name('Underline', self.get_iter_position(), iter)
else:
self.remove_all_tags(self.get_iter_position(), iter)
win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
I figured it out rather than using
open(filename, 'w').write(content)
to save the content I imported GLib and used
GLib.file_set_contents(filename, content)