Last active
September 5, 2020 14:31
-
-
Save rBrenick/ac0487ed06ebdbc413a63c20f6107d67 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
__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() |
Author
rBrenick
commented
Sep 5, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment