| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- import os.path
- from pathlib import Path
- from PySide2 import QtCore, QtWidgets
- from PySide2.QtCore import Qt
- class FilenameModel(QtCore.QStringListModel):
- """
- Model used by QCompleter for file name completions.
- Constructor options:
- `filter_` (None, 'dirs') - include all entries or folders only
- `fs_engine` ('qt', 'pathlib') - enumerate files using `QDir` or `pathlib`
- `icon_provider` (func, 'internal', None) - a function which gets path
- and returns QIcon
- """
- def __init__(self, filter_=None, fs_engine='qt', icon_provider='internal'):
- super().__init__()
- self.current_path = None
- self.fs_engine = fs_engine
- self.filter = filter_
- if icon_provider == 'internal':
- self.icons = QtWidgets.QFileIconProvider()
- self.icon_provider = self.get_icon
- else:
- self.icon_provider = icon_provider
- def data(self, index, role):
- "Get names/icons of files"
- default = super().data(index, role)
- if role == Qt.DecorationRole and self.icon_provider:
- # self.setData(index, dat, role)
- return self.icon_provider(super().data(index, Qt.DisplayRole))
- if role == Qt.DisplayRole:
- return Path(default).name
- return default
- def get_icon(self, path):
- "Internal icon provider"
- return self.icons.icon(QtCore.QFileInfo(path))
- def get_file_list(self, path):
- "List entries in `path` directory"
- lst = None
- if self.fs_engine == 'pathlib':
- lst = self.sort_paths([i for i in path.iterdir()
- if self.filter != 'dirs' or i.is_dir()])
- elif self.fs_engine == 'qt':
- qdir = QtCore.QDir(str(path))
- qdir.setFilter(qdir.NoDotAndDotDot | qdir.Hidden |
- (qdir.Dirs if self.filter == 'dirs' else qdir.AllEntries))
- names = qdir.entryList(sort=QtCore.QDir.DirsFirst |
- QtCore.QDir.LocaleAware)
- lst = [str(path / i) for i in names]
- return lst
- @staticmethod
- def sort_paths(paths):
- "Windows-Explorer-like filename sorting (for 'pathlib' engine)"
- dirs, files = [], []
- for i in paths:
- if i.is_dir():
- dirs.append(str(i))
- else:
- files.append(str(i))
- return sorted(dirs, key=str.lower) + sorted(files, key=str.lower)
- def setPathPrefix(self, prefix):
- path = Path(prefix)
- if not prefix.endswith(os.path.sep):
- path = path.parent
- if path == self.current_path:
- return # already listed
- if not path.exists():
- return # wrong path
- self.setStringList(self.get_file_list(path))
- self.current_path = path
- class MenuListView(QtWidgets.QMenu):
- """
- QMenu with QListView.
- Supports `activated`, `clicked`, `setModel`.
- """
- max_visible_items = 16
- def __init__(self, parent=None):
- super().__init__(parent)
- self.listview = lv = QtWidgets.QListView()
- lv.setFrameShape(lv.NoFrame)
- lv.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
- pal = lv.palette()
- pal.setColor(pal.Base, self.palette().color(pal.Window))
- lv.setPalette(pal)
- act_wgt = QtWidgets.QWidgetAction(self)
- act_wgt.setDefaultWidget(lv)
- self.addAction(act_wgt)
- self.activated = lv.activated
- self.clicked = lv.clicked
- self.setModel = lv.setModel
- lv.sizeHint = self.size_hint
- lv.minimumSizeHint = self.size_hint
- lv.mousePressEvent = self.mouse_press_event
- lv.mouseMoveEvent = self.update_current_index
- lv.setMouseTracking(True) # receive mouse move events
- lv.leaveEvent = self.clear_selection
- lv.mouseReleaseEvent = self.mouse_release_event
- lv.keyPressEvent = self.key_press_event
- lv.setFocusPolicy(Qt.NoFocus) # no focus rect
- lv.setFocus()
- self.last_index = QtCore.QModelIndex() # selected index
- self.flag_mouse_l_pressed = False
- def key_press_event(self, event):
- key = event.key()
- if key in (Qt.Key_Return, Qt.Key_Enter):
- if self.last_index.isValid():
- self.activated.emit(self.last_index)
- self.close()
- elif key == Qt.Key_Escape:
- self.close()
- elif key in (Qt.Key_Down, Qt.Key_Up):
- model = self.listview.model()
- row_from, row_to = 0, model.rowCount()-1
- if key == Qt.Key_Down:
- row_from, row_to = row_to, row_from
- if self.last_index.row() in (-1, row_from): # no index=-1
- index = model.index(row_to, 0)
- else:
- shift = 1 if key == Qt.Key_Down else -1
- index = model.index(self.last_index.row()+shift, 0)
- self.listview.setCurrentIndex(index)
- self.last_index = index
- def update_current_index(self, event):
- self.last_index = self.listview.indexAt(event.pos())
- self.listview.setCurrentIndex(self.last_index)
- def clear_selection(self, event=None):
- self.listview.clearSelection()
- # selectionModel().clear() leaves selected item in Fusion theme
- self.listview.setCurrentIndex(QtCore.QModelIndex())
- self.last_index = QtCore.QModelIndex()
-
- def mouse_press_event(self, event):
- if event.button() == Qt.LeftButton:
- self.flag_mouse_l_pressed = True
- self.update_current_index(event)
- def mouse_release_event(self, event):
- """
- When item is clicked w/ left mouse button close menu, emit `clicked`.
- Check if there was left button press event inside this widget.
- """
- if event.button() == Qt.LeftButton and self.flag_mouse_l_pressed:
- self.flag_mouse_l_pressed = False
- if self.last_index.isValid():
- self.clicked.emit(self.last_index)
- self.close()
- def size_hint(self):
- lv = self.listview
- width = lv.sizeHintForColumn(0)
- width += lv.verticalScrollBar().sizeHint().width()
- if isinstance(self.parent(), QtWidgets.QToolButton):
- width = max(width, self.parent().width())
- visible_rows = min(self.max_visible_items, lv.model().rowCount())
- return QtCore.QSize(width, visible_rows * lv.sizeHintForRow(0))
|