Skip to content

Instantly share code, notes, and snippets.

@rBrenick
Last active September 5, 2020 14:31
Show Gist options
  • Save rBrenick/ac0487ed06ebdbc413a63c20f6107d67 to your computer and use it in GitHub Desktop.
Save rBrenick/ac0487ed06ebdbc413a63c20f6107d67 to your computer and use it in GitHub Desktop.
__author__ = "Richard Brenick - [email protected]"
__created__ = "2020-09-05"
__license__ = "MIT License"
import os
from Qt import QtCore, QtWidgets
class QtFilePath(QtWidgets.QWidget):
def __init__(self, parent=None,
settings_name="_DefaultQtFilePathSettings",
start_dir="",
file_filter="",
use_directory_dialog=False,
relative_to_path="",
recent_paths_amount=30
):
"""
QWidget for file paths.
Includes "browse" button and list of recent file paths.
:param parent: Qt Parent
:param settings_name: name for settings .ini
:param start_dir: start folder for file dialog
:param file_filter: filter for the QFileDialog
:param use_directory_dialog: browse for folder instead of file path
:param relative_to_path: show paths relative to this path
:param recent_paths_amount: clamp recent paths to this amount
"""
super(QtFilePath, self).__init__(parent)
# safety convert, just in case we get passed negative float values for some reason
recent_paths_amount = int(abs(recent_paths_amount))
self.start_dir = start_dir
self.relative_to_path = relative_to_path
self.relative_to_path_drive = os.path.splitdrive(self.relative_to_path)[0]
self.use_directory_dialog = use_directory_dialog
self.recent_paths_amount = recent_paths_amount
# settings object to store data between sessions
self._settings = QtFilePathSettings(identifier=settings_name,
recent_paths_amount=recent_paths_amount,
relative_to_path=relative_to_path
)
main_layout = QtWidgets.QHBoxLayout()
# surprise, it's a QComboBox for the path display
self.path_CB = QtWidgets.QComboBox()
self.path_CB.addItems(self._settings.get_recent_paths())
self.path_CB.setEditable(True)
self.path_CB.setCurrentText("")
self.path_CB.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed)
main_layout.addWidget(self.path_CB)
# Browse path button
self.browse_file_BTN = QtWidgets.QPushButton("...")
self.browse_file_BTN.setMaximumWidth(40)
self.browse_file_BTN.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
self.browse_file_BTN.clicked.connect(self._open_dialog_and_set_path)
main_layout.addWidget(self.browse_file_BTN)
self.setLayout(main_layout)
# create path dialog instance for later use
self.path_dialog = QtWidgets.QFileDialog()
if self.use_directory_dialog:
self.path_dialog.setFileMode(QtWidgets.QFileDialog.DirectoryOnly)
self.path_dialog.setNameFilter(file_filter)
def _open_dialog_and_set_path(self):
file_path = self.get_dialog_path()
if file_path:
self.set_path(clean_path(file_path))
def get_dialog_path(self):
# set starting directory for dialog
start_dir = self.start_dir
# no start_dir specified, use folder of most recent path
if not start_dir:
start_dir = str(self._settings.value(QtFilePathSettings.key_most_recent_dir, defaultValue=""))
self.path_dialog.setDirectory(start_dir)
# display dialog
if self.path_dialog.exec_():
selected_paths = self.path_dialog.selectedFiles()
self._settings.setValue(QtFilePathSettings.key_most_recent_dir, os.path.dirname(selected_paths[0]))
return selected_paths[0]
def set_path(self, path):
"""
Set path display in ComboBox and store in settings
:param path:
:return:
"""
self._settings.add_recent_path(path) # store full path in settings, then convert to relative if desired
# convert to relative path
path_drive = os.path.splitdrive(path)[0]
if self.relative_to_path and path_drive == self.relative_to_path_drive:
path = clean_path(os.path.relpath(path, self.relative_to_path))
# if path has already been added to ComboBox, remove the old one
path_index_map = {self.path_CB.itemText(i): i for i in range(self.path_CB.count())}
if path in path_index_map.keys():
self.path_CB.removeItem(path_index_map[path])
# add to ComboBox and set as active path
self.path_CB.insertItem(0, path)
self.path_CB.setCurrentIndex(0)
# clamp amount of recent paths
while self.path_CB.count() > self.recent_paths_amount:
self.path_CB.removeItem(self.recent_paths_amount)
def path(self):
"""get path from widget"""
current_path = self.path_CB.currentText()
# join with relative_to_path if it's a relative path
if self.relative_to_path and os.path.splitdrive(current_path)[0] != "":
return clean_path(os.path.abspath(os.path.join(self.relative_to_path, current_path)))
return current_path
###################################################
# Convenience functions for replacing LineEdit with this widget
def text(self):
return self.path()
def setText(self, value):
self.set_path(value)
class QtFilePathSettings(QtCore.QSettings):
key_recent_paths = "recent_paths"
key_most_recent_dir = "most_recent_dir"
def __init__(self, identifier="_DefaultQtFilePathSettings", recent_paths_amount=30, relative_to_path=""):
# C:\Users\USERNAME\AppData\Roaming\QtFilePath\_DefaultQtFilePathSettings.ini
super(QtFilePathSettings, self).__init__(
QtCore.QSettings.IniFormat,
QtCore.QSettings.UserScope,
'QtFilePath',
identifier
)
self.recent_paths_amount = recent_paths_amount
self.relative_to_path = relative_to_path
self.relative_to_path_drive = os.path.splitdrive(self.relative_to_path)[0]
def get_recent_paths(self, full_paths=False):
"""
Get recent paths from settings
:param full_paths: skip converting to relative paths
:return:
"""
paths = self.value(self.key_recent_paths)
if not isinstance(paths, list):
if paths:
# QSettings ini sometimes has trouble reading data types
paths = str(paths).split(", ")
else:
paths = []
# convert to relative paths before returning
if not full_paths and self.relative_to_path:
relative_paths = []
for full_path in paths:
if os.path.splitdrive(full_path)[0] != self.relative_to_path_drive:
# if the path is on a separate drive then we can't get a relative path
relative_paths.append(full_path)
else:
relative_paths.append(clean_path(os.path.relpath(full_path, self.relative_to_path)))
paths = relative_paths
return paths
def add_recent_path(self, path):
"""
Add path to recent paths in settings
:param path: <str>
:return:
"""
recent_paths = self.get_recent_paths(full_paths=True)
# remove path from recent_paths if it's been added previously
if path in recent_paths:
recent_paths.remove(path)
recent_paths.insert(0, path)
if len(recent_paths) > self.recent_paths_amount: # clamp amount of paths
recent_paths = recent_paths[:self.recent_paths_amount]
self.setValue(self.key_recent_paths, recent_paths)
def clean_path(p):
"""simple method to remove \\ from windows paths"""
return p.replace("\\", "/")
def main():
app = QtWidgets.QApplication()
app.setApplicationDisplayName("QtFilePath Example")
file_path_widget = QtFilePath()
file_path_widget.show()
file_path_widget.resize(QtCore.QSize(600, 200))
app.exec_()
if __name__ == '__main__':
main()
@rBrenick
Copy link
Author

rBrenick commented Sep 5, 2020

QtFilePath

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment