99 QListView , QComboBox , QPushButton , QToolButton , QSizePolicy ,
1010 QMenu , QAction , QLineEdit , QCheckBox , QTableView , QHeaderView )
1111from 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
1515from qtvcp .widgets .widget_baseclass import _HalWidgetBase
1616from qtvcp .core import Status , Action , Info
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+
3451class 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