How can I trigger the changing of an icon when the user clicks the Icon only for the items/rows in the list which are of type File. Each row in the treeview contains an object in the UserRole called TreeItem which stores if the item is marked as a favorite and also the filepath.
In short is there a way to know if a the Decoration 'icon' is clicked by the user?
The tool simply just goes through a directory recursively and collects the files and folders.
class TreeItem(object):
def __init__(self, filepath):
self.filepath = filepath
self.isFavorite = False
Dropbox links to icons
https://www.dropbox.com/s/3pt0ev2un7eoswh/file_off.svg?dl=0
https://www.dropbox.com/s/xext3m9d4atd3i6/file_on.svg?dl=0
https://www.dropbox.com/s/6d750av0y77hq0g/folder.svg?dl=0
Be sure to change the directory path for testing
Tool Code:
import sys
import os
from PySide import QtGui, QtCore, QtSvg
DIR_ICON_PATH = 'folder.svg'
FILE_ICON_OFF = 'file_off.svg'
FILE_ICON_ON = 'file_on.svg'
class TreeItem(object):
def __init__(self, filepath):
self.filepath = filepath
self.isFavorite = False
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
# formatting
self.resize(550, 400)
self.setWindowTitle("Toychest")
# widgets
self.treeview = QtGui.QTreeView()
self.treeview.setHeaderHidden(True)
self.treeview.setUniformRowHeights(True)
self.treeview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.source_model = QtGui.QStandardItemModel()
self.treeview.setModel(self.source_model)
# signals
self.treeview.doubleClicked.connect(self.doubleClickedItem)
# main layout
mainLayout = QtGui.QGridLayout()
mainLayout.setContentsMargins(0,0,0,0)
mainLayout.addWidget(self.treeview)
self.setLayout(mainLayout)
self.initDirectory('C:/Users/jmartini/Downloads')
# Functions
# ------------------------------------------------------------------------------
def initDirectory(self, path):
new_item = self.newItem(path)
self.readDirectory(path, new_item)
self.source_model.appendRow(new_item)
def readDirectory(self, path, parent_item):
directory = os.listdir(path)
for file_name in directory:
file_path = path + '/' + file_name
new_item = self.newItem(file_path)
parent_item.appendRow(new_item)
if os.path.isdir(file_path):
self.readDirectory(file_path, new_item)
def newItem(self, path):
# create Object
obj = TreeItem(path)
title = os.path.basename(path)
item = QtGui.QStandardItem()
item.setData(obj, role=QtCore.Qt.UserRole)
icon_path = FILE_ICON_OFF
if os.path.isdir(path):
icon_path = DIR_ICON_PATH
icon = QtGui.QIcon(icon_path)
item.setText(title)
item.setIcon(icon)
return item
def doubleClickedItem(self, idx):
if not idx.isValid():
return
obj = idx.data(QtCore.Qt.UserRole)
print obj.filepath, obj.isFavorite
# print idx.parent(), idx.parent().isValid()
# model = idx.model()
# print model.index(idx.row(), 0, parent=idx.parent()).data()
# Main
# ------------------------------------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
You have to implement the mousePressEvent and compare the mouse position with icon position and proceed accordingly.
You can use QStandardItemModel.itemFromIndex in order to access to the underlying item. The model can be obtained via QModelIndex.model. So the following would do:
item = idx.model().itemFromIndex(idx)
obj = idx.data(QtCore.Qt.UserRole).toPyObject()
obj.isFavorite = (obj.isFavorite + 1) % 2
if obj.isFavorite:
item.setIcon(QtGui.QIcon(FILE_ICON_ON))
else:
item.setIcon(QtGui.QIcon(FILE_ICON_OFF))
If required you can access obj.filepath in order to check whether the clicked item corresponds to a file.
Related
I'm populating a QTreeview from a specified folder directory. However I'm not entirely clear on how to properly adjust the code to make the Treeview better reflect the nesting of the folders. In this case I only want to show folders that contain OBJ files within them.
and I want it to looks like this
import os, sys
from Qt import QtGui, QtWidgets, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.resize(350,500)
self.ui_navigator = QtWidgets.QComboBox()
self.ui_files = QtWidgets.QTreeView()
self.ui_files.setHeaderHidden(True)
self.ui_files.setModel(QtGui.QStandardItemModel())
self.ui_files.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.ui_files.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.ui_files.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.setCentralWidget(self.ui_files)
self.populate_files()
def populate_files(self, files=[]):
model = self.ui_files.model()
model.clear()
root = 'C:/Users/jmartini/Desktop/Trash/models'
for subdir, dirs, files in os.walk(root):
folder_item = QtGui.QStandardItem(os.path.basename(subdir).upper())
folder_item.setData(subdir, role=QtCore.Qt.UserRole)
folder_item.setData(QtGui.QColor(QtGui.QColor(200, 140, 70, 255)), role=QtCore.Qt.ForegroundRole)
fnt = folder_item.font()
fnt.setBold(True)
folder_item.setData(fnt, role=QtCore.Qt.FontRole)
model.appendRow(folder_item)
for file in files:
if file.lower().endswith('.obj'):
filepath = os.path.join(subdir, file)
name = os.path.basename(file)
item = QtGui.QStandardItem(name)
item.setData(filepath, role=QtCore.Qt.UserRole)
folder_item.appendRow(item)
model.sort(0, QtCore.Qt.AscendingOrder)
def main():
app = QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Using QFileInfo and QDir:
def populate_files(self):
model = self.ui_files.model()
model.clear()
exts = ["*.obj"]
root = 'C:/Users/jmartini/Desktop/Trash/models'
def addItems(parent, path):
finfo = QtCore.QFileInfo(path)
if finfo.isFile():
if "*."+finfo.suffix() in exts:
it = QtGui.QStandardItem(finfo.fileName())
it.setData(finfo.absoluteFilePath(), QtCore.Qt.UserRole)
parent.appendRow(it)
elif finfo.isDir():
iterator = QtCore.QDirIterator(finfo.absoluteFilePath(), exts, QtCore.QDir.Files, QtCore.QDirIterator.Subdirectories)
if iterator.hasNext():
it = QtGui.QStandardItem(finfo.fileName().upper())
it.setData(finfo.absoluteFilePath(), QtCore.Qt.UserRole)
parent.appendRow(it)
for subfiles in QtCore.QDir(finfo.absoluteFilePath()).entryInfoList([], QtCore.QDir.AllEntries|QtCore.QDir.NoDotAndDotDot):
addItems(it, subfiles.absoluteFilePath())
for finfo in QtCore.QDir(root).entryInfoList([], QtCore.QDir.AllEntries|QtCore.QDir.NoDotAndDotDot):
addItems(model, finfo.absoluteFilePath())
self.ui_files.expandAll()
I'm facing an issue to save a complete state of a Qt window application.
What I'd like to di is save a file which contains everything about the window and then, if I want, I could reload it to obtain the same window with all previous variable in it (reload their values)
For instance, I've this code
import pickle
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.centralWidget = QWidget()
self.color = self.centralWidget.palette().color(QPalette.Background)
self.setCentralWidget(self.centralWidget)
self.plotview = QGroupBox(" ")
self.layout_plotview = QVBoxLayout()
self.test = QLineEdit('0.0')
self.Button_Loadstate = QPushButton('Load state')
self.Button_Savestate = QPushButton('Save state')
self.layout_plotview.addWidget(self.test)
self.layout_plotview.addWidget(self.Button_Savestate)
self.layout_plotview.addWidget(self.Button_Loadstate)
self.centralWidget.setLayout(self.layout_plotview)
self.Button_Savestate.clicked.connect(self.Savestate)
self.Button_Loadstate.clicked.connect(self.Loadstate)
def Savestate(self,):
fileName = QFileDialog.getSaveFileName(self,'Save State')
if (fileName[0] == '') :
return
file_pi = open(fileName[0] + '.state', 'wb')
pickle.dump(self, file_pi)
file_pi.close()
return
def Loadstate(self,):
fileName = QFileDialog.getOpenFileName(self,'Load State' , "", "data files (*.state)")
if (fileName[0] == '') :
return
filehandler = open(fileName[0], 'rb')
self = pickle.load(filehandler)
filehandler.close()
return
def main():
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
which give me this :
I create a window with a QLineEdit and two buttons (one for saving and one for loading). Then I use pickle to save the self of the SurfViewer class instance in a Savestatefunction. Then I try to reload the previous state with pickle self = pickle.load(filehandler). It seems that it will not be so simple because I cannot assign the self variable.
If anyone could give some insights to perform that without saving every variable by hand.
I have written a function in Python which goes through a specified directory and gets all its files and sub-folders recursively and filtering it to only list certain file formats. How do I display this in a treeview with an different icon for folders vs files?
recursive directory function:
def test(self):
formats = ['.jpg', '.jpeg', '.txt']
for path, subdirs, files in os.walk(r'C:/Users/jmartini/Projects/Photogrammetry'):
for file in files:
filename, extension = os.path.splitext(file)
if (extension.lower() in formats):
f = os.path.join(path, file)
print f
Concept
Entire application code:
import sys
import os
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
# formatting
self.resize(550, 400)
self.setWindowTitle("Toychest")
# widgets
self.toollist = QtGui.QTreeView()
# Tabs
# signals
# main layout
mainLayout = QtGui.QGridLayout()
mainLayout.setContentsMargins(0,0,0,0)
mainLayout.addWidget(self.toollist)
self.setLayout(mainLayout)
# self.test()
# Functions
# ------------------------------------------------------------------------------
def test(self):
formats = ['.jpg', '.jpeg', '.txt']
for path, subdirs, files in os.walk(r'C:/Users/jmartini/Projects/Photogrammetry'):
for file in files:
filename, extension = os.path.splitext(file)
if (extension.lower() in formats):
f = os.path.join(path, file)
print f
# Main
# ------------------------------------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
When adding a new item to the tree, it first has to find the item in the tree that it should be parented to. For example
path = '/path/to/file.jpg'
To create this item in the tree, I first need to find the top-level item for path. Then, I need to find a child of that item for to. Then, I can add the file.jpg item.
def find_or_create_item(self, path, parent=None):
if parent is None:
parent = self.tree.invisibleRootItem()
names = path.lstrip('/').split('/')
name = names[0]
for i in range(parent.childCount()):
child = parent.child(i)
if child.text(0) == name:
item = child
break
else:
item = QTreeWidgetItem(name)
parent.addChild(item)
if names[1:]:
return self.find_or_create_item('/'.join(names[1:], item)
else:
return item
Try to use QStandardItemModel and QStandardItem when you build directory tree.
Use setIcon method of QStandardItem
For example:
import os
import PyQt4.QtGui
class Example(QtGui.QWidget):
DEFAULT_DIR_PATH = 'C:/Users/jmartini/Projects/Photogrammetry'
DIR_ICON_PATH = 'dir/icon/path/dir.jpg'
FILE_ICON_PATH = 'file/icon/path/file.jpg'
def __init__(self):
super(Example, self).__init__()
self.initUI()
self.initDirectory(DEFAULT_DIR_PATH)
def initUI(self):
# formatting
self.resize(550, 400)
self.setWindowTitle("Toychest")
# widgets
self.toollist = QtGui.QTreeView()
# QTreeView use QStandardItemModel as data source
self.source_model = QtGui.QStandardItemModel()
# Tabs
# signals
# main layout
mainLayout = QtGui.QGridLayout()
mainLayout.setContentsMargins(0,0,0,0)
mainLayout.addWidget(self.toollist)
self.setLayout(mainLayout)
# set model for toollist
self.toollist.setModel(self.source_model)
def initDirectory(self, path):
new_item = newItem(path)
self.readDirectory(path, new_item)
self.source_model.appendRow(new_item)
def readDirectory(self, path, parent_item):
directory = os.listdir(path)
for file_name in directory:
file_path = path + '/' + file_name
new_item = newItem(file_path)
parent_item.appendRow(new_item)
if os.path.isdir(file_path):
self.readDirectory(file_path, new_item)
def newItem(self, path):
title = os.path.basename(path)
item = QtGui.QStandardItem()
icon_path = FILE_ICON_PATH
if os.path.isdir(file_path):
icon_path = DIR_ICON_PATH
icon = QtGui.QIcon(icon_path)
item.setText(title)
item.setIcon(icon)
return item
You can check here a PyQT5 function to list directory structure using QTreeWidgetItem
I have a class to create a PyQt4 widget and another class to parse xml to get inputs. I want to create an UI dyanamically add buttons to the widget, reading from the xml(s) passed, which is not happening:
import sys
import xml.etree.ElementTree as ET
from PyQt4 import QtGui
ui = None
class userInterface(QtGui.QWidget):
def __init__(self):
super(userInterface, self).__init__()
def getFilesWindow(self):
self.filesSelected = []
fDialog = QtGui.QFileDialog.getOpenFileNames(self, 'Open file', '/Learning/Python/substance_XML_reading/', "SBS (*.sbs)")
for path in fDialog:
if path:self.filesSelected.append(path)
return self.filesSelected
class ParseXML():
def getXMLTags(self,fileList):
self.tags = []
if fileList:
print fileList
for eachFile in fileList:
fileToParse = ET.parse(eachFile)
root = fileToParse.getroot()
for child in root:
self.tags.append(child.tag)
return self.tags
def getSetUI(flist):
global ui
if flist:
tagsForBtns = ParseXML().getXMLTags(flist)
print tagsForBtns
for eachitem in tagsForBtns:
btn = QtGui.QPushButton(ui,eachitem)
def main():
app = QtGui.QApplication(sys.argv)
ui = userInterface()
fileListForUIGen = ui.getFilesWindow() # will return a list of files
getSetUI(fileListForUIGen) # Parses each file, gets tags, creates buttons and has to add to the current window..NOT WORKING
ui.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In order to add buttons to a widget, you need to place them inside a QLayout
class Widget(QtGui.QWidget):
def __init__(self):
super(Widget, self).__init__()
self.ui_lay = QtGui.QVBoxLayout()
self.setLayout(self.ui_lay)
def addButton(self, text):
btn = QtGui.QPushButton(text, self)
self.ui_lay.addWidget(btn)
...
for eachitem in tagsForBtns:
ui.addButton(eachitem)
Also, make sure to use global ui in your main() function if you're going to be doing it this way.
Personally, I don't see the reasons for splitting up all the function calls. I would just put them all in the UserInterface class.
In the example below, I would like to fill my QListWidget with files opening a Qdialog. I don't understand how I can add the files selected in the list. Should I do a new class? How can I connect the two methods setupList and addFiles?
import sys
from PyQt4 import QtCore, QtGui
from datapath import *
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.sources =[]
self.setupActions()
self.setupList()
self.setupUi()
self.setupStatusBars()
def addFiles(self):
files = QtGui.QFileDialog.getOpenFileNames(
self,"Open File", dirpath, "txt Files (*.txt)")
for string in files:
self.sources.append(str(string))
return self.sources
def setupActions(self):
self.exitAct = QtGui.QAction(
QtGui.QIcon(':/images/exit.png'),
"E&xit", self, shortcut="Ctrl+Q",
statusTip="Exit the application", triggered=self.close
)
self.addFilesAct = QtGui.QAction(
QtGui.QIcon(':/images/open.png'),
"Add &Files", self, shortcut=QtGui.QKeySequence.Open,
statusTip="Open an existing file", triggered=self.addFiles
)
def setupList(self):
#FileList
self.FileList = QtGui.QListWidget(self)
self.FileList.addItems(self.sources)
def setupUi(self):
#Window size
horiz = 300
vert = 300
self.setGeometry(self.width()/2, self.height()/2,horiz,vert)
self.setWindowTitle("test")
#MenuBar
self.FileMenu = self.menuBar().addMenu("&File")
self.FileMenu.addAction(self.addFilesAct)
self.FileMenu.addSeparator();
self.FileMenu.addAction(self.exitAct)
#ToolBar
self.fileToolBar = self.addToolBar("Open")
self.fileToolBar.addAction(self.addFilesAct)
self.fileToolBar.setIconSize(QtCore.QSize(64,64))
#Build Layout
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self.FileList)
widget = QtGui.QWidget()
widget.setLayout(mainLayout)
self.setCentralWidget(widget)
def setupStatusBars(self):
self.statusBar().showMessage("Ready")
def main():
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
When self.sources is changed, the elements of the QListWidget will not change. So self.FileList.addItems(self.sources) in setupList() should be removed and instead put in addFiles() so that every time the files are selected in the dialog, the QListWidget's addItems method is called. Then return self.sources in addFiles() would be unnecessary.
In order to append files to the listwidget, the addFiles method should look like this:
def addFiles(self):
files = QtGui.QFileDialog.getOpenFileNames(
self, "Open File", dirpath, "txt Files (*.txt)")
for string in files:
self.FileList.addItem(string)
The source list looks like it might be redundant. If you need to get the full list of files, you can do something like this:
sources = []
for row in range(self.FileList.count()):
item = self.FileList.item(row)
# python3
sources.append(item.text())
# python2, convert to python strings
# sources.append(unicode(item.text()))
print(sources)