1313"""Contains a class for a widget that represents a 'Open Project Directory' dialog."""
1414
1515import os
16- from PySide6 .QtCore import QDir , QModelIndex , QStandardPaths , Qt , Slot
16+ from PySide6 .QtCore import QDir , QModelIndex , QStandardPaths , Qt , Slot , QPoint , QTimer
1717from PySide6 .QtGui import QAction , QKeySequence , QValidator
18- from PySide6 .QtWidgets import QAbstractItemView , QComboBox , QDialog , QFileSystemModel
18+ from PySide6 .QtWidgets import QAbstractItemView , QComboBox , QDialog , QFileSystemModel , QApplication
1919from spinetoolbox .helpers import ProjectDirectoryIconProvider
2020from spinetoolbox .widgets .custom_menus import OpenProjectDialogComboBoxContextMenu
2121from spinetoolbox .widgets .notification import Notification
@@ -33,14 +33,14 @@ def __init__(self, toolbox):
3333 """
3434 from ..ui import open_project_dialog # pylint: disable=import-outside-toplevel
3535
36- super ().__init__ (parent = toolbox , f = Qt .Dialog ) # Setting the parent inherits the stylesheet
36+ super ().__init__ (parent = toolbox , f = Qt .WindowType . Dialog ) # Setting the parent inherits the stylesheet
3737 self ._qsettings = toolbox .qsettings ()
3838 # Set up the user interface from Designer file
3939 self .ui = open_project_dialog .Ui_Dialog ()
4040 self .ui .setupUi (self )
4141 self .combobox_context_menu = None
4242 # Ensure this dialog is garbage-collected when closed
43- self .setAttribute (Qt .WA_DeleteOnClose )
43+ self .setAttribute (Qt .WidgetAttribute . WA_DeleteOnClose )
4444 self .setWindowFlag (Qt .WindowType .WindowContextHelpButtonHint , False )
4545 # QActions for keyboard shortcuts
4646 self .go_root_action = QAction (self )
@@ -51,12 +51,13 @@ def __init__(self, toolbox):
5151 self .selected_path = ""
5252 self .cb_ss = self .ui .comboBox_current_path .styleSheet ()
5353 self .file_model = CustomQFileSystemModel ()
54- self .file_model .setFilter (QDir .AllDirs | QDir .NoDotAndDotDot )
54+ self .file_model .setFilter (QDir .Filter . AllDirs | QDir . Filter .NoDotAndDotDot )
5555 self .icon_provider = ProjectDirectoryIconProvider ()
5656 self .file_model .setIconProvider (self .icon_provider )
57- self .file_model .setRootPath (QDir .rootPath ())
57+ self .file_model .setRootPath (QDir .homePath ())
58+ self .ui .treeView_file_system .setUniformRowHeights (True )
5859 self .ui .treeView_file_system .setModel (self .file_model )
59- self .file_model .sort (0 , Qt .AscendingOrder )
60+ self .file_model .sort (0 , Qt .SortOrder . AscendingOrder )
6061 # Enable validator (experimental, not very useful here)
6162 # Validator prevents typing Invalid strings to combobox. (not in use)
6263 # When text in combobox is Intermediate, the validator prevents emitting
@@ -81,8 +82,10 @@ def __init__(self, toolbox):
8182 self .ui .comboBox_current_path .setCurrentIndex (- 1 )
8283 self .file_model .directoryLoaded .connect (self .expand_and_resize )
8384 # Start browsing to start index immediately when dialog is shown
84- self .start_path = self .file_model .filePath (start_index )
85- self .starting_up = True
85+ self ._start_path = self .file_model .filePath (start_index )
86+ self ._starting_up = True
87+ self ._loading = False
88+ self ._closing = False
8689 self .ui .treeView_file_system .setCurrentIndex (start_index )
8790 self .connect_signals ()
8891
@@ -110,30 +113,59 @@ def connect_signals(self):
110113 self .ui .treeView_file_system .clicked .connect (self .set_selected_path )
111114 self .ui .treeView_file_system .doubleClicked .connect (self .open_project )
112115 self .ui .treeView_file_system .selectionModel ().currentChanged .connect (self .current_changed )
116+ self .ui .treeView_file_system .expanded .connect (self .on_expanded )
117+ self .file_model .rowsInserted .connect (self .on_rows_inserted )
118+ self .file_model .layoutChanged .connect (self .on_layout_changed )
113119 self .go_root_action .triggered .connect (self .go_root )
114120 self .go_home_action .triggered .connect (self .go_home )
115121 self .go_documents_action .triggered .connect (self .go_documents )
116122 self .go_desktop_action .triggered .connect (self .go_desktop )
117123
124+ def start_busy (self ):
125+ if not self ._loading :
126+ self ._loading = True
127+ # prevent stacking
128+ if QApplication .overrideCursor () is None :
129+ QApplication .setOverrideCursor (Qt .CursorShape .BusyCursor )
130+ QTimer .singleShot (2000 , self .stop_busy )
131+
132+ def stop_busy (self ):
133+ if self ._loading :
134+ self ._loading = False
135+ if QApplication .overrideCursor () is not None :
136+ QApplication .restoreOverrideCursor ()
137+
138+ @Slot (QModelIndex )
139+ def on_expanded (self , index ):
140+ if self ._closing :
141+ return
142+ self .start_busy ()
143+
144+ @Slot (QModelIndex , int , int )
145+ def on_rows_inserted (self , parent , start , end ):
146+ self .stop_busy ()
147+
148+ @Slot (list , str )
149+ def on_layout_changed (self ):
150+ self .stop_busy ()
151+
118152 @Slot (str )
119153 def expand_and_resize (self , p ):
120154 """Expands, resizes, and scrolls the tree view to the current directory
121- when the file model has finished loading the path. Slot for the file
122- model's directoryLoaded signal. The directoryLoaded signal is emitted only
123- if the directory has not been cached already. Note, that this is
124- only used when the open project dialog is opened
155+ when the file model has finished loading the initial path. The directoryLoaded
156+ signal is emitted only if the directory has not been cached already.
125157
126158 Args:
127159 p (str): Directory that has been loaded
128160 """
129- if self .starting_up :
161+ if self ._starting_up :
130162 current_index = self .ui .treeView_file_system .currentIndex ()
131- self .ui .treeView_file_system .scrollTo (current_index , hint = QAbstractItemView .PositionAtTop )
163+ self .ui .treeView_file_system .scrollTo (current_index , hint = QAbstractItemView .ScrollHint . PositionAtTop )
132164 self .ui .treeView_file_system .expand (current_index )
133- if p == self .start_path :
165+ if p == self ._start_path :
134166 self .ui .treeView_file_system .resizeColumnToContents (0 )
135167 self .set_selected_path (current_index )
136- self .starting_up = False
168+ self ._starting_up = False
137169
138170 @Slot ()
139171 def validator_state_changed (self ):
@@ -162,12 +194,9 @@ def current_index_changed(self, i):
162194 self .remove_directory_from_recents (p , self ._qsettings )
163195 return
164196 fm_index = self .file_model .index (p )
165- self .ui .treeView_file_system .collapseAll ()
166- self .ui .treeView_file_system .setCurrentIndex (fm_index )
167- self .ui .treeView_file_system .expand (fm_index )
168- self .ui .treeView_file_system .scrollTo (fm_index , hint = QAbstractItemView .PositionAtTop )
197+ self .collapse_and_expand (fm_index )
169198
170- @Slot (" QModelIndex" , " QModelIndex" , name = "current_changed" )
199+ @Slot (QModelIndex , QModelIndex , name = "current_changed" )
171200 def current_changed (self , current , previous ):
172201 """Processed when the current item in file system tree view has been
173202 changed with keyboard or mouse. Updates the text in combobox.
@@ -178,7 +207,7 @@ def current_changed(self, current, previous):
178207 """
179208 self .set_selected_path (current )
180209
181- @Slot (" QModelIndex" , name = "set_selected_path" )
210+ @Slot (QModelIndex , name = "set_selected_path" )
182211 def set_selected_path (self , index ):
183212 """Sets the text in the combobox as the selected path in the file system tree view.
184213
@@ -211,56 +240,56 @@ def go_root(self, checked=False):
211240 """
212241 self .ui .comboBox_current_path .setCurrentIndex (- 1 )
213242 root_index = self .file_model .index (QDir .rootPath ())
214- self .ui .treeView_file_system .collapseAll ()
215- self .ui .treeView_file_system .setCurrentIndex (root_index )
216- self .ui .treeView_file_system .expand (root_index )
217- self .ui .treeView_file_system .scrollTo (root_index , hint = QAbstractItemView .PositionAtTop )
243+ self .collapse_and_expand (root_index )
218244
219245 @Slot (bool , name = "go_home" )
220246 def go_home (self , checked = False ):
221247 """Slot for the 'Home' button. Scrolls the treeview to show and select the user's home directory."""
222248 self .ui .comboBox_current_path .setCurrentIndex (- 1 )
223249 home_index = self .file_model .index (QDir .homePath ())
224- self .ui .treeView_file_system .collapseAll ()
225- self .ui .treeView_file_system .setCurrentIndex (home_index )
226- self .ui .treeView_file_system .expand (home_index )
227- self .ui .treeView_file_system .scrollTo (home_index , hint = QAbstractItemView .PositionAtTop )
250+ self .collapse_and_expand (home_index )
228251
229252 @Slot (bool , name = "go_documents" )
230253 def go_documents (self , checked = False ):
231254 """Slot for the 'Documents' button. Scrolls the treeview to show and select the user's documents directory."""
232- docs = QStandardPaths .writableLocation (QStandardPaths .DocumentsLocation )
255+ docs = QStandardPaths .writableLocation (QStandardPaths .StandardLocation . DocumentsLocation )
233256 if not docs :
234257 return
235258 self .ui .comboBox_current_path .setCurrentIndex (- 1 )
236259 docs_index = self .file_model .index (docs )
237- self .ui .treeView_file_system .collapseAll ()
238- self .ui .treeView_file_system .setCurrentIndex (docs_index )
239- self .ui .treeView_file_system .expand (docs_index )
240- self .ui .treeView_file_system .scrollTo (docs_index , hint = QAbstractItemView .PositionAtTop )
260+ self .collapse_and_expand (docs_index )
241261
242262 @Slot (bool , name = "go_desktop" )
243263 def go_desktop (self , checked = False ):
244264 """Slot for the 'Desktop' button. Scrolls the treeview to show and select the user's desktop directory."""
245- desktop = QStandardPaths .writableLocation (QStandardPaths .DesktopLocation ) # Return a list
265+ desktop = QStandardPaths .writableLocation (QStandardPaths .StandardLocation . DesktopLocation ) # Return a list
246266 if not desktop :
247267 return
248268 self .ui .comboBox_current_path .setCurrentIndex (- 1 )
249269 desktop_index = self .file_model .index (desktop )
270+ self .collapse_and_expand (desktop_index )
271+
272+ def collapse_and_expand (self , index ):
273+ """Collapses all open branches, sets current index to given index, expands the branch, and
274+ scrolls the view to the new index.
275+
276+ Args:
277+ index (QModelIndex): New index
278+ """
250279 self .ui .treeView_file_system .collapseAll ()
251- self .ui .treeView_file_system .setCurrentIndex (desktop_index )
252- self .ui .treeView_file_system .expand (desktop_index )
253- self .ui .treeView_file_system .scrollTo (desktop_index , hint = QAbstractItemView .PositionAtTop )
280+ self .ui .treeView_file_system .setCurrentIndex (index )
281+ self .ui .treeView_file_system .expand (index )
282+ self .ui .treeView_file_system .scrollTo (index , hint = QAbstractItemView . ScrollHint .PositionAtTop )
254283
255- @Slot (" QModelIndex" )
284+ @Slot (QModelIndex )
256285 def open_project (self , index ):
257286 """Opens project if index contains a valid Spine Toolbox project.
258287 Slot for the mouse doubleClicked signal. Prevents showing the
259288 'Not a valid spine toolbox project' notification if user just wants
260289 to collapse a directory.
261290
262291 Args:
263- index (QModelIndex): File model index which was double clicked
292+ index (QModelIndex): File model index which was double- clicked
264293 """
265294 if not index .isValid ():
266295 return
@@ -277,6 +306,8 @@ def done(self, r):
277306 Args:
278307 r (int) Return code
279308 """
309+ self ._closing = True
310+ self ._force_reset_cursor ()
280311 if r == QDialog .DialogCode .Accepted :
281312 if not os .path .isdir (self .selection ()):
282313 notification = Notification (self , "Path does not exist" )
@@ -292,6 +323,12 @@ def done(self, r):
292323 self .update_recents (os .path .abspath (os .path .join (self .selection (), os .path .pardir )), self ._qsettings )
293324 super ().done (r )
294325
326+ def _force_reset_cursor (self ):
327+ """Forces the cursor back to normal when the dialog is closed by double-clicking a project."""
328+ self ._loading = False
329+ while QApplication .overrideCursor () is not None :
330+ QApplication .restoreOverrideCursor ()
331+
295332 @staticmethod
296333 def update_recents (entry , qsettings ):
297334 """Adds a new entry to QSettings variable that remembers the five most recent project storages.
@@ -340,7 +377,7 @@ def remove_directory_from_recents(p, qsettings):
340377 qsettings .setValue ("appSettings/recentProjectStorages" , updated_recents )
341378 qsettings .sync () # Commit change immediately
342379
343- @Slot (" QPoint" )
380+ @Slot (QPoint )
344381 def show_context_menu (self , pos ):
345382 """Shows the context menu for the QCombobox with a 'Clear history' entry.
346383
0 commit comments