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
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()
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.
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.
I have a list which gets one element each time user opens a file. I need to create a button with the file's name (element from the list), each time this file is appended to a list, and put this button into a scroll-area.
The problem is that I always have only one button, that just changes its name:
filenames = []
def addfiles():
fileName = QtGui.QFileDialog.getOpenFileName()
fileDirectory = unicode(fileName)
global filenames
filenames.append(fileDirectory)
button = QtGui.QPushButton(os.path.basename(fileDirectory))
window.scrollArea.setWidget(button)
I know that the problem is that I add the same object (button) to the scroll-area, but I don't know how to fix it.
The Problem is not that you add the same button, but that you sort of replace the Widget in the scrollArea.
A better way would be to create a QHBoxLayout and than add the buttons to the layout.
filenames = []
lay = QtGui.QHboxLayout()
window.scrollArea.setLayout(lay)
def addfiles():
fileName= QtGui.QFileDialog.getOpenFileName()
fileDirectory = unicode(fileName)
global filenames
filenames.append(fileDirectory)
button = QtGui.QPushButton(os.path.basename(fileDirectory))
lay.addWidget(button)
In a sort of that way it should work. Here is a small working example:
from PyQt4 import QtGui
import sys
filenames = []
class TestGui(QtGui.QWidget):
""" A Fast test gui show how to create buttons in a ScrollArea"""
def __init__(self):
super(TestGui, self).__init__()
self.lay = QtGui.QHBoxLayout()
self.sA = QtGui.QScrollArea()
self.sA_lay = QtGui.QVBoxLayout()
self.sA.setLayout(self.sA_lay)
self.closeGui = QtGui.QPushButton("Close")
self.add_file_button = QtGui.QPushButton("Add File")
self.lay.addWidget(self.closeGui)
self.lay.addWidget(self.add_file_button)
self.lay.addWidget(self.sA)
self.setLayout(self.lay)
self.connect_()
self.show()
def connect_(self):
self.add_file_button.clicked.connect(self.__add_file_to_list)
self.closeGui.clicked.connect(self.close)
return
def __add_file_to_list(self):
fname = QtGui.QFileDialog.getOpenFileName()
global filenames
filenames.append(fname)
button = QtGui.QPushButton(fname)
self.sA_lay.addWidget(button)
return
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
tg = TestGui()
sys.exit(app.exec_())
The problem is that you're not adding a layout to the scrollLayout, you're setting the scrollArea's widget:
#!/usr/bin/env python
import os, sys
from PyQt4 import QtCore, QtGui
filenames = []
class Window(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.centralwidget = QtGui.QWidget(self)
self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget)
self.scrollArea = QtGui.QScrollArea(self.centralwidget)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QtGui.QWidget(self.scrollArea)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.verticalLayout.addWidget(self.scrollArea)
self.setCentralWidget(self.centralwidget)
# create a layout for your scrollarea
self.formLayout = QtGui.QFormLayout(self.scrollAreaWidgetContents)
self.addFiles()
def addFiles(self):
global filenames
filenames.append("~/files/newFile.txt")
button = QtGui.QPushButton(os.path.basename(filenames[-1]))
self.formLayout.addWidget(button)
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)