How to select and edit newly created folder in QTreeView using PYQT5 - python

I am trying to figure out how to select and edit newly created folder. Here is a bit of code demonstrating it:
import os
import sys
from PyQt5.QtWidgets import (QApplication,
QMainWindow,
QLabel,
QLineEdit,
QPushButton,
QShortcut,
QFileSystemModel,
QTreeView,
QWidget,
QVBoxLayout,
QHBoxLayout,
QLayout,
QMenu,
QPlainTextEdit,
QSizePolicy,
QMessageBox,
QAbstractItemView)
from PyQt5.QtCore import QSize, Qt, QRect
from PyQt5.QtGui import QKeySequence
class FSView(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setFixedSize(size.width()*1/4, size.height()*0.85)
self.model = QFileSystemModel()
self.model.setRootPath('')
self.model.setReadOnly(False)
self.tree = QTreeView()
self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree.customContextMenuRequested.connect(self.openMenu)
self.tree.setModel(self.model)
self.tree.setAnimated(False)
self.tree.setIndentation(20)
self.tree.setDragDropMode(QAbstractItemView.InternalMove)
windowLayout = QVBoxLayout()
windowLayout.addWidget(self.tree)
self.setLayout(windowLayout)
def openMenu(self, position):
menu = QMenu()
menu.addAction('New folder', self.NewF)
menu.exec_(self.tree.viewport().mapToGlobal(position))
def NewF(self):
d = str(self.model.filePath(self.tree.currentIndex())) + '/New folder'
if not os.path.exists(d):
os.mkdir(d)
# correctIndex = self.tree.currentIndex() + 1 #not working
# self.tree.edit(correctIndex)
if __name__ == '__main__':
app = QApplication(sys.argv)
screen = app.primaryScreen()
size = screen.size()
ex = FSView()
ex.show()
sys.exit(app.exec_())
After creating the new folder, I would like it to be selected and in edit mode at same time (i.e.: self.tree.edit(correctIndex)).
I have checked some posts (here) but still have not managed to get the correct index.
Thanks for suggestions.

Using your code you must first obtain the QModelIndex using the index() method of QFileSystemModel passing it the path, and then call the setCurrentIndex() and edit() methods of QTreeView.
def NewF(self):
d = str(self.model.filePath(self.tree.currentIndex())) + '/New folder'
if not os.path.exists(d):
os.mkdir(d)
ix = self.model.index(d)
QTimer.singleShot(0, lambda ix=ix: self.tree.setCurrentIndex(ix))
QTimer.singleShot(0, lambda ix=ix: self.tree.edit(ix))
Or use the mkdir() method of QFileSystemModel as shown below:
def NewF(self):
ci = self.tree.currentIndex()
ix = self.model.mkdir(ci, "New folder")
QTimer.singleShot(0, lambda ix=ix : self.tree.setCurrentIndex(ix))
QTimer.singleShot(0, lambda ix=ix : self.tree.edit(ix))

Related

Pictures in pyqt5 gridlayout look stretched when window is maximized

The window shows a set of pictures. Once the window is maximized the app shows all pics in the window so it resizes every image looking ugly. I'd like to prevent the streching and keep the aspect ratio. Any clue?
This is the code of the proof of concept:
QApplication, QListWidget, QAbstractItemView, QPushButton, QWidget,
QTabWidget, QVBoxLayout, QHBoxLayout, QLabel, QPlainTextEdit,
QTextEdit, QCheckBox, QFileDialog, QMessageBox, QGridLayout from
PyQt5.QtCore import QProcess, QTimer, Qt from PyQt5.QtGui import QFont, QPixmap import os from QLabelClickable import QLabelClickable
class ExampleApp(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'Example'
self.left = 0
self.top = 0
self.width = 1000
self.height = 800
self.setMaximumWidth(1000)
self.setMaximumHeight(800)
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.show()
data = []
for root, dirs, files in os.walk('img/'):
for file in files:
filepath = os.path.join(root, file)
data.append(filepath)
self.picture_window = PictureWindow(data=data)
self.picture_window.show()
class PictureWindow(QWidget):
def __init__(self, data):
super().__init__()
self.n_columns = 5
self.data = data
self.gridLayout = QGridLayout()
for i, filename in enumerate(self.data):
self.render_picture(filename=filename, pos=i)
self.setLayout(self.gridLayout)
def render_picture(self, filename, pos):
image_widget = QLabelClickable(pos)
image_widget.setGeometry(15, 15, 118, 130)
image_widget.setToolTip(filename)
image_widget.setCursor(Qt.PointingHandCursor)
self.pixmapImagen = QPixmap(filename).scaled(375, 375, Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_widget.setPixmap(self.pixmapImagen)
image_widget.setAlignment(Qt.AlignCenter)
cell_layout = QVBoxLayout()
cell_layout.setAlignment(Qt.AlignHCenter)
cell_layout.addWidget(image_widget)
self.gridLayout.addLayout(cell_layout, int(pos / self.n_columns), pos % self.n_columns)
from PyQt5.QtCore import Qt, pyqtSignal, QTimer
from PyQt5.QtWidgets import QApplication, QDialog, QLabel, QMessageBox
class QLabelClickable(QLabel):
clicked = pyqtSignal(str)
def __init__(self, id, parent=None):
super(QLabelClickable, self).__init__(parent)
self.id = id
def mousePressEvent(self, event):
self.ultimo = "Click"
def mouseReleaseEvent(self, event):
if self.ultimo == "Click":
QTimer.singleShot(QApplication.instance().doubleClickInterval(),
self.performSingleClickAction)
else:
self.clicked.emit(self.ultimo+"-"+str(self.id))
def mouseDoubleClickEvent(self, event):
self.ultimo = "Double Click"
def performSingleClickAction(self):
if self.ultimo == "Click":
self.clicked.emit(self.ultimo+"-"+str(self.id))
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = ExampleApp()
sys.exit(app.exec_())
This is how it looks when window is normal:
And looks like this when window is maximized:
After a few days trying to figure out what's going on here I an't go further, could you please help me to spot anything wrong?
I expect the pics to keepe their aspect ratio to implement a ScrollArea later on.

PyQt5 resize widgets automatically when changing window size

I'm dealing with my first Pyqt5 app and I'm having an hard time understanding how to make my widgets change size according to the change of the window size (like when the window is Maximized). This is my code:
import sys
from PyQt5.QtWidgets import (
QApplication,
QHBoxLayout,
QVBoxLayout,
QFrame,
QListWidget,
QPlainTextEdit,
QWidget,
)
# from PyQt5 import QtCore, QtGui,
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Title")
self.centralwidget = QWidget(self)
self.centralwidgetHorizontalLayout = QHBoxLayout(self.centralwidget)
self.citationsFrame = QFrame(self.centralwidget)
self.citationsFrameVerticalLayout = QVBoxLayout(self.citationsFrame)
self.citationPlainTextWidget = QPlainTextEdit(self.citationsFrame)
self.citationsFrameVerticalLayout.addWidget(self.citationPlainTextWidget)
self.citationListWidget = QListWidget(self.citationsFrame)
self.citationsFrameVerticalLayout.addWidget(self.citationListWidget)
self.centralwidgetHorizontalLayout.addWidget(self.citationsFrame)
self.centralwidgetHorizontalLayout.addWidget(self.citationsFrame)
self.referencesFrame = QFrame(self.centralwidget)
self.referencesFrameHorizontalLayout = QHBoxLayout(self.referencesFrame)
self.referencesListWidget = QListWidget(self.referencesFrame)
self.referencesListWidget.setDragEnabled(True)
self.referencesFrameHorizontalLayout.addWidget(self.referencesListWidget)
self.centralwidgetHorizontalLayout.addWidget(self.referencesFrame)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
What am I doing wrong?

connect pushbuttons to lineedits

I have a problem with my python code, it's a QTablewidget with Qpushbuttons and Qline_edits inside each cell, We want a file path (after selecting this file from the file browser) to be written in the line edit when we click on its pushbutton and to store the output files so we can use them after.
from PyQt5.QtWidgets import (
QMainWindow,
QApplication,
QPushButton,
QFileDialog,
QGridLayout,
QLineEdit,
QTableWidget,
QHBoxLayout,
QWidget,
)
from PyQt5.QtCore import pyqtSlot
import sys
from functools import partial
global list_C
list_C=[]
class boxlayout(QWidget):
def __init__(self):
super(boxlayout,self).__init__()
layout=QHBoxLayout()
layout.setContentsMargins(0,0,0,0)
layout.setSpacing(0)
boxlayout.le=QLineEdit()
boxlayout.btn=QPushButton()
layout.addWidget(boxlayout.le)
layout.addWidget(boxlayout.btn)
self.setLayout(layout)
class Main(QMainWindow):
def __init__(self):
super().__init__()
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
mainlayout = QGridLayout(centralWidget)
self.table = QTableWidget(self)
self.table.resize(640, 480)
self.table.setColumnCount(3)
self.table.setRowCount(4)
mainlayout.addWidget(self.table)
save_button=QPushButton('OK')
mainlayout.addWidget(save_button)
save_button.clicked.connect(self.save_as_list)
for i in range(4):
for j in range(3):
self.table.setCellWidget(i,j, boxlayout())
boxlayout.btn.clicked.connect(partial(self.open_dialog, boxlayout.le))
#pyqtSlot(QLineEdit)
def open_dialog(self, le: QLineEdit):
file_name = QFileDialog.getOpenFileName(
self,
"Open File",
"${HOME}",
"All Files (*)",
)
le.setText(file_name[0])
def save_as_list(self):
for i in range(4):
for j in range(3):
item=self.table.cellWidget(i,j).le.text()
list_C.append(item)
print(list_C) #output=['last le value','last le value',...]
if __name__ == "__main__":
app = QApplication(sys.argv)
main_gui = Main()
main_gui.show()
sys.exit(app.exec())
Here is a minimal example:
from PyQt5.QtWidgets import (
QMainWindow,
QApplication,
QPushButton,
QFileDialog,
QFrame,
QGridLayout,
QLineEdit,
)
from PyQt5.QtCore import pyqtSlot
import sys
from functools import partial
class Main(QMainWindow):
def __init__(self):
super().__init__()
top_frame = QFrame(self)
self.setCentralWidget(top_frame)
self.grid = QGridLayout(top_frame)
for i in range(10):
btn = QPushButton(top_frame)
le = QLineEdit(top_frame)
btn.clicked.connect(partial(self.open_dialog, le))
btn.setText("open file system dialog")
self.grid.addWidget(btn, i, 0)
self.grid.addWidget(le, i, 1)
#pyqtSlot(QLineEdit)
def open_dialog(self, le: QLineEdit):
file_name = QFileDialog.getOpenFileName(
self,
"Open File",
"${HOME}",
"All Files (*)",
)
le.setText(file_name[0])
if __name__ == "__main__":
app = QApplication(sys.argv)
main_gui = Main()
main_gui.show()
sys.exit(app.exec())
The key thing here is to pass the QLineEdit to the file selector method.
Here we are doing it with: lambda: self.open_dialog(le)

PyQt5: Want to start a specific subprocess with a button click

One day old in terms of experience with PyQT, I followed sample code HERE to do this, but I am clueless as to how I could separate the start download part from the start GUI part, so that I can instead start that when I press the OK (startBtn)button. Also, know the command I do doesn't do anything but give you an error, but I know that works.
Any help appreciated!
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QAction, qApp, QDesktopWidget, QPushButton, QHBoxLayout, QVBoxLayout, QTextEdit
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QThread, QProcess
import sys
class GUI(QProcess):
def __init__(self):
super().__init__()
# Create an instance variable here (of type QTextEdit)
startBtn = QPushButton('OK')
stopBtn = QPushButton('Cancel')
#startBtn.clicked.connect()
stopBtn.clicked.connect(qApp.exit)
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(startBtn)
self.hbox.addWidget(stopBtn)
self.edit = QTextEdit()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addWidget(self.edit)
self.vbox.addLayout(self.hbox)
#setLayout(self.vbox)
self.central=QWidget()
#self.vbox.addWidget(self.edit)
self.central.setLayout(self.vbox)
self.central.show()
def readStdOutput(self):
self.edit.append(str(self.readAllStandardOutput()))
def main():
app = QApplication(sys.argv)
qProcess = GUI()
qProcess.setProcessChannelMode(QProcess.MergedChannels);
qProcess.start("youtube-dl")
qProcess.readyReadStandardOutput.connect(qProcess.readStdOutput);
return app.exec_()
if __name__ == '__main__':
main()
2 notes:
If you also know how to disable the OK button when you press it, until the process is finished, then I'd love to know.
Not all imports are used, but I can clean that later. PyCharm show which is used and not. Cleanup is for later.
To do what you ask you have to have some considerations:
youtube-dl requires parameters, like the url, for this I have placed a QLineEdit.
To know when the process starts and ends, we use the signal: stateChanged(newState)
Complete code:
import sys
from PyQt5.QtCore import QProcess
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QTextEdit, QLabel, QLineEdit
class GUI(QProcess):
def __init__(self, parent=None):
super(GUI, self).__init__(parent=parent)
# Create an instance variable here (of type QTextEdit)
self.startBtn = QPushButton('OK')
self.stopBtn = QPushButton('Cancel')
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(self.startBtn)
self.hbox.addWidget(self.stopBtn)
self.label = QLabel("Url: ")
self.lineEdit = QLineEdit()
self.lineEdit.textChanged.connect(self.EnableStart)
self.hbox2 = QHBoxLayout()
self.hbox2.addWidget(self.label)
self.hbox2.addWidget(self.lineEdit)
self.edit = QTextEdit()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addLayout(self.hbox2)
self.vbox.addWidget(self.edit)
self.vbox.addLayout(self.hbox)
self.central = QWidget()
self.central.setLayout(self.vbox)
self.central.show()
self.startBtn.clicked.connect(self.startDownload)
self.stopBtn.clicked.connect(self.kill)
self.stateChanged.connect(self.slotChanged)
self.EnableStart()
def slotChanged(self, newState):
if newState == QProcess.NotRunning:
self.startBtn.setDisabled(False)
elif newState == QProcess.Running:
self.startBtn.setDisabled(True)
def startDownload(self):
self.start("youtube-dl", [self.lineEdit.text()])
def readStdOutput(self):
self.edit.append(str(self.readAllStandardOutput()))
def EnableStart(self):
self.startBtn.setDisabled(self.lineEdit.text() == "")
def main():
app = QApplication(sys.argv)
qProcess = GUI()
qProcess.setProcessChannelMode(QProcess.MergedChannels)
qProcess.readyReadStandardOutput.connect(qProcess.readStdOutput)
return app.exec_()
if __name__ == '__main__':
main()
Screenshot:

How to enable/disable a item in a QTreeView eventbased?

I want to enable/disable a checkable item in a QTreeWidget, when a specific signal is sent.
The following code dows not work:
model = QStandardItemModel()
view = QTreeView()
view.setModel(model)
rootItem = QStandardItem()
rootItem = model.invisibleRootItem()
categoryItem = QStandardItem(item)
categoryItem.setCheckable(True)
rootItem.appendRow(categoryItem)
signalSource.availabilityChanged.connect(categoryItem.setEnabled)
It produces the error:
TypeError: unhashable type: 'PySide.QtGui.QStandardItem'
Is there a solution for changing the state or data of a QStandardItem via signal/slot?
This looks like a bug in PySide, as connect should accept any callable (the example code works correctly in PyQt4).
As a workaround, try wrapping QStandardItem methods in a lambda:
signalSource.availabilityChanged.connect(
lambda enable: categoryItem.setEnabled(enable))
EDIT
To connect the items in a loop, use a default argument, like this:
for button in buttonList:
item = QStandardItem("Test")
...
button.toggled.connect(
lambda enable, item=item: item.setEnabled(enable))
With the help of ekhumoros answer, I found a way to get my problem solved, but it seems to be an ugly workaround in PySide, using the sender to get the signal hooked up correctly.
import sys
import argparse
import signal
#import sip
#sip.setapi('QString', 2)
#from PyQt4.QtGui import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QStandardItemModel, QStandardItem, QTreeView
from PySide.QtGui import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QStandardItemModel, QStandardItem, QTreeView
class MainWindow(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
buttonList = []
for i in xrange(10):
button = QPushButton("1")
button.setCheckable(True)
buttonList.append(button)
model = QStandardItemModel()
view = QTreeView()
view.setModel(model)
layout = QVBoxLayout()
self.setLayout(layout)
buttonLayout = QHBoxLayout()
layout.addLayout(buttonLayout)
for button in buttonList:
buttonLayout.addWidget(button)
layout.addWidget(view)
rootItem = QStandardItem()
rootItem = model.invisibleRootItem()
self.itemList = {}
for button in buttonList:
item = QStandardItem("Test")
item.setCheckable(True)
rootItem.appendRow(item)
self.itemList[button] = item
# Works with PyQt4, but not with PySide
#button.toggled.connect(item.setEnabled)
# Workaround for PySide
button.toggled.connect(self.workaround)
for button in buttonList:
button.setChecked(True)
def workaround(self, enable):
self.itemList[self.sender()].setEnabled(enable)
def main(argv):
app = QApplication(argv)
w = MainWindow()
w.show()
retcode = app.exec_()
if __name__ == "__main__":
main(sys.argv)
Just using a lambda construct didn't work in the loop. It just connect all the signal with the last reference to iterate over.

Categories