Skip to content

Commit 2196102

Browse files
committed
qtvco -file_manager: add ability to restrict searchable paths.
Jim shared code for optionally limiting where a user can search. Thanks Jim.
1 parent 68fcc46 commit 2196102

1 file changed

Lines changed: 97 additions & 21 deletions

File tree

lib/python/qtvcp/widgets/file_manager.py

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
QListView, QComboBox, QPushButton, QToolButton, QSizePolicy,
1010
QMenu, QAction, QLineEdit, QCheckBox, QTableView, QHeaderView)
1111
from PyQt5.QtGui import QIcon
12-
from PyQt5.QtCore import (QModelIndex, QDir, Qt, pyqtSlot,
13-
QItemSelectionModel, QItemSelection, pyqtProperty)
12+
from PyQt5.QtCore import (QModelIndex, QDir, Qt, pyqtSlot, pyqtProperty, pyqtSignal,
13+
QItemSelectionModel, QItemSelection, QSortFilterProxyModel)
1414

1515
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
1616
from qtvcp.core import Status, Action, Info
@@ -31,6 +31,23 @@
3131
# Force the log level for this module
3232
# LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
3333

34+
class RestrictedProxyModel(QSortFilterProxyModel):
35+
# Proxy model to filter out directories above a certain root path.
36+
def __init__(self, root_path, parent=None):
37+
super().__init__(parent)
38+
self.base_path = QDir(root_path).absolutePath()
39+
40+
def filterAcceptsRow(self, source_row, source_parent):
41+
index = self.sourceModel().index(source_row, 0, source_parent)
42+
if not index.isValid():
43+
return False
44+
file_path = self.sourceModel().filePath(index)
45+
if self.sourceModel().fileName(index) == ".." and source_parent == self.sourceModel().index(self.base_path):
46+
return False
47+
# Only allow paths inside the base path
48+
return file_path.startswith(self.base_path)
49+
50+
3451
class FileManager(QWidget, _HalWidgetBase):
3552
def __init__(self, parent=None):
3653
super(FileManager, self).__init__(parent)
@@ -42,6 +59,9 @@ def __init__(self, parent=None):
4259
self._last = 0
4360
self._doubleClick = False
4461
self._showListView = False
62+
self.role = 'User'
63+
self.restricted_path = '/home'
64+
self.restricted = False
4565

4666
if INFO.PROGRAM_PREFIX is not None:
4767
self.user_path = os.path.expanduser(INFO.PROGRAM_PREFIX)
@@ -217,15 +237,16 @@ def _hal_init(self):
217237
# install jump paths into toolbutton menu
218238
for i in self._jumpList:
219239
self.addAction(i)
220-
221-
# set recorded columns sort settings
222-
self.SETTINGS_.beginGroup("FileManager-{}".format(self.objectName()))
223-
sect = self.SETTINGS_.value('sortIndicatorSection', type = int)
224-
order = self.SETTINGS_.value('sortIndicatorOrder', type = int)
225-
self.SETTINGS_.endGroup()
226-
if not None in(sect,order):
227-
self.table.horizontalHeader().setSortIndicator(sect,order)
228-
240+
try:
241+
# set recorded columns sort settings
242+
self.SETTINGS_.beginGroup("FileManager-{}".format(self.objectName()))
243+
sect = self.SETTINGS_.value('sortIndicatorSection', type = int)
244+
order = self.SETTINGS_.value('sortIndicatorOrder', type = int)
245+
self.SETTINGS_.endGroup()
246+
if not None in(sect,order):
247+
self.table.horizontalHeader().setSortIndicator(sect,order)
248+
except:
249+
pass
229250
self.connectSelection()
230251

231252
# when qtvcp closes this gets called
@@ -262,6 +283,10 @@ def folderChanged(self, data):
262283

263284
def updateDirectoryView(self, path, quiet = False):
264285
if os.path.exists(path):
286+
self.list.selectionModel().clearSelection()
287+
self.list.setRootIndex(QModelIndex())
288+
self.table.selectionModel().clearSelection()
289+
self.table.setRootIndex(QModelIndex())
265290
self.list.setRootIndex(self.model.setRootPath(path))
266291
self.table.setRootIndex(self.model.setRootPath(path))
267292
else:
@@ -276,21 +301,35 @@ def filterChanged(self, index):
276301

277302
def listClicked(self, index):
278303
# the signal passes the index of the clicked item
279-
dir_path = os.path.normpath(self.model.filePath(index))
280-
if self.model.fileInfo(index).isFile():
304+
if self.restricted:
305+
source_index = self.proxy_model.mapToSource(index)
306+
file_info = self.model.fileInfo(source_index)
307+
else:
308+
file_info = self.model.fileInfo(index)
309+
dir_path = os.path.normpath(file_info.filePath())
310+
if file_info.isFile():
281311
self.currentPath = dir_path
282312
self.textLine.setText(self.currentPath)
283313
return
284314
else:
285315
self.currentPath = None
286-
root_index = self.model.setRootPath(dir_path)
287-
self.list.setRootIndex(root_index)
288-
self.table.setRootIndex(root_index)
316+
self.textLine.setText(os.path.abspath(dir_path))
317+
if self.restricted:
318+
self.update_proxy_index(dir_path)
319+
else:
320+
self.currentPath = None
321+
self.updateDirectoryView(dir_path)
289322

290323
def onUserClicked(self):
324+
if self.restricted: return
325+
self.role = 'User'
326+
self.restricted_path = self.user_path
291327
self.showUserDir()
292328

293329
def onMediaClicked(self):
330+
if self.restricted: return
331+
self.role = 'Media'
332+
self.restricted_path = self.media_path
294333
self.showMediaDir()
295334

296335
# jump directly to a saved path shown on the button
@@ -306,9 +345,9 @@ def onJumpClicked(self):
306345
self.updateDirectoryView(temp)
307346
else:
308347
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, 'file jumppath: {} not valid'.format(data))
309-
log.debug('file jumopath: {} not valid'.format(data))
348+
log.debug('file jumppath: {} not valid'.format(data))
310349
else:
311-
self.jumpButton.setText('User')
350+
self.jumpButton.setText(self.role)
312351

313352
# jump directly to a saved path from the menu
314353
def jumpTriggered(self, data):
@@ -341,7 +380,7 @@ def onActionClicked(self):
341380
self.settingMenu.clear()
342381
for key in self._jumpList:
343382
self.addAction(key)
344-
self.jumpButton.setText('User')
383+
self.jumpButton.setText(self.role)
345384
except Exception as e:
346385
print(e)
347386

@@ -381,6 +420,27 @@ def paste(self):
381420
# helper functions
382421
########################
383422

423+
def setRestricted(self):
424+
# once set restricted, it can't be undone or done again
425+
if self.restricted: return
426+
self.restricted = True
427+
self.proxy_model = RestrictedProxyModel(self.restricted_path)
428+
self.proxy_model.setSourceModel(self.model)
429+
index = self.model.index(self.restricted_path)
430+
self.proxy_index = self.proxy_model.mapFromSource(index)
431+
432+
self.list.setModel(self.proxy_model)
433+
self.table.setModel(self.proxy_model)
434+
self.list.setRootIndex(self.proxy_index)
435+
self.table.setRootIndex(self.proxy_index)
436+
437+
def update_proxy_index(self, path):
438+
index = self.model.index(path)
439+
self.proxy_index = self.proxy_model.mapFromSource(index)
440+
self.list.setRootIndex(self.proxy_index)
441+
self.table.setRootIndex(self.proxy_index)
442+
self.model.setRootPath(path)
443+
384444
def addAction(self, i):
385445
action = QAction(QIcon.fromTheme('user-home'), i, self)
386446
# weird lambda i=i to work around 'function closure'
@@ -409,9 +469,11 @@ def showCopyControls(self, state):
409469
self.pasteButton.hide()
410470

411471
def showMediaDir(self, quiet = False):
472+
self.jumpButton.setText('Media')
412473
self.updateDirectoryView(self.media_path, quiet)
413474

414475
def showUserDir(self, quiet = False):
476+
self.jumpButton.setText('User')
415477
self.updateDirectoryView(self.user_path, quiet)
416478

417479
def copyFile(self, s, d):
@@ -482,14 +544,23 @@ def getCurrentSelected(self):
482544
else:
483545
selectionModel = self.table.selectionModel()
484546
index = selectionModel.currentIndex()
485-
dir_path = os.path.normpath(self.model.filePath(index))
486-
if self.model.fileInfo(index).isFile():
547+
if self.restricted:
548+
source_index = self.proxy_model.mapToSource(index)
549+
file_info = self.model.fileInfo(source_index)
550+
dir_path = os.path.normpath(self.model.filePath(source_index))
551+
else:
552+
file_info = self.model.fileInfo(index)
553+
dir_path = os.path.normpath(self.model.filePath(index))
554+
if file_info.isFile():
487555
return (dir_path, True)
488556
else:
489557
return (dir_path, False)
490558

491559
# This can be class patched to do something else
492560
def load(self, fname=None):
561+
result = self.getCurrentSelected()
562+
print(f'Load" {fname} {result}')
563+
return
493564
try:
494565
if fname is None:
495566
self._getPathActivated()
@@ -557,4 +628,9 @@ def resetShowListView(self, state):
557628
app = QApplication(sys.argv)
558629
gui = FileManager()
559630
gui.show()
631+
#gui.PREFS_ = True
632+
gui._hal_init()
633+
gui.connectSelection()
634+
gui.onUserClicked()
635+
gui.setRestricted()
560636
sys.exit(app.exec_())

0 commit comments

Comments
 (0)