10522:c04e878aa308 | 10523:e4069ddd7dc7 |
---|---|
5 | 5 |
6 """ | 6 """ |
7 Module implementing a file manager for MicroPython devices. | 7 Module implementing a file manager for MicroPython devices. |
8 """ | 8 """ |
9 | 9 |
10 import contextlib | |
10 import os | 11 import os |
11 import shutil | 12 import shutil |
12 | 13 |
13 from PyQt6.QtCore import QPoint, Qt, pyqtSlot | 14 from PyQt6.QtCore import QPoint, Qt, pyqtSlot |
14 from PyQt6.QtWidgets import ( | 15 from PyQt6.QtWidgets import ( |
84 self.getButton.setEnabled(False) | 85 self.getButton.setEnabled(False) |
85 self.getAsButton.setEnabled(False) | 86 self.getAsButton.setEnabled(False) |
86 | 87 |
87 self.openButton.setEnabled(False) | 88 self.openButton.setEnabled(False) |
88 self.saveButton.setEnabled(False) | 89 self.saveButton.setEnabled(False) |
90 self.saveAsButton.setEnabled(False) | |
89 | 91 |
90 self.localFileTreeWidget.header().setSortIndicator( | 92 self.localFileTreeWidget.header().setSortIndicator( |
91 0, Qt.SortOrder.AscendingOrder | 93 0, Qt.SortOrder.AscendingOrder |
92 ) | 94 ) |
93 self.deviceFileTreeWidget.header().setSortIndicator( | 95 self.deviceFileTreeWidget.header().setSortIndicator( |
142 self.__localMenu.addSeparator() | 144 self.__localMenu.addSeparator() |
143 act = self.__localMenu.addAction(self.tr("Show Hidden Files")) | 145 act = self.__localMenu.addAction(self.tr("Show Hidden Files")) |
144 act.setCheckable(True) | 146 act.setCheckable(True) |
145 act.setChecked(Preferences.getMicroPython("ShowHiddenLocal")) | 147 act.setChecked(Preferences.getMicroPython("ShowHiddenLocal")) |
146 act.triggered[bool].connect(self.__localHiddenChanged) | 148 act.triggered[bool].connect(self.__localHiddenChanged) |
149 self.__localMenu.addSeparator() | |
150 self.__localClearSelectionAct = self.__localMenu.addAction( | |
151 self.tr("Clear Selection"), self.__clearLocalSelection | |
152 ) | |
147 | 153 |
148 self.__deviceMenu = QMenu(self) | 154 self.__deviceMenu = QMenu(self) |
149 if not isMicrobitDeviceWithMPy: | 155 if not isMicrobitDeviceWithMPy: |
150 self.__deviceMenu.addAction( | 156 self.__deviceMenu.addAction( |
151 self.tr("Change Directory"), self.__changeDeviceDirectory | 157 self.tr("Change Directory"), self.__changeDeviceDirectory |
175 if not isMicrobitDeviceWithMPy: | 181 if not isMicrobitDeviceWithMPy: |
176 self.__deviceMenu.addSeparator() | 182 self.__deviceMenu.addSeparator() |
177 self.__deviceMenu.addAction( | 183 self.__deviceMenu.addAction( |
178 self.tr("Show Filesystem Info"), self.__showFileSystemInfo | 184 self.tr("Show Filesystem Info"), self.__showFileSystemInfo |
179 ) | 185 ) |
186 self.__deviceMenu.addSeparator() | |
187 self.__deviceClearSelectionAct = self.__deviceMenu.addAction( | |
188 self.tr("Clear Selection"), self.__clearDeviceSelection | |
189 ) | |
180 | 190 |
181 def start(self): | 191 def start(self): |
182 """ | 192 """ |
183 Public method to start the widget. | 193 Public method to start the widget. |
184 """ | 194 """ |
195 self.__viewmanager = ericApp().getObject("ViewManager") | |
196 self.__viewmanager.editorCountChanged.connect(self.__updateSaveButtonStates) | |
197 | |
185 dirname = "" | 198 dirname = "" |
186 vm = ericApp().getObject("ViewManager") | 199 aw = self.__viewmanager.activeWindow() |
187 aw = vm.activeWindow() | |
188 if aw and FileSystemUtilities.isPlainFileName(aw.getFileName()): | 200 if aw and FileSystemUtilities.isPlainFileName(aw.getFileName()): |
189 dirname = os.path.dirname(aw.getFileName()) | 201 dirname = os.path.dirname(aw.getFileName()) |
190 if not dirname: | 202 if not dirname: |
191 dirname = ( | 203 dirname = ( |
192 Preferences.getMicroPython("MpyWorkspace") | 204 Preferences.getMicroPython("MpyWorkspace") |
193 or Preferences.getMultiProject("Workspace") | 205 or Preferences.getMultiProject("Workspace") |
194 or os.path.expanduser("~") | 206 or os.path.expanduser("~") |
195 ) | 207 ) |
196 self.__listLocalFiles(dirname) | 208 self.__listLocalFiles(dirname=dirname) |
197 | 209 |
198 if self.__repl.deviceSupportsLocalFileAccess(): | 210 if self.__repl.deviceSupportsLocalFileAccess(): |
199 dirname = self.__repl.getDeviceWorkspace() | 211 dirname = self.__repl.getDeviceWorkspace() |
200 if dirname: | 212 if dirname: |
201 self.__listLocalFiles(dirname, True) | 213 self.__listLocalFiles(dirname=dirname, localDevice=True) |
202 return | 214 return |
203 | 215 |
204 # list files via device script | 216 # list files via device script |
217 self.__expandedDeviceEntries = [] | |
205 self.__fileManager.pwd() | 218 self.__fileManager.pwd() |
206 | 219 |
207 def stop(self): | 220 def stop(self): |
208 """ | 221 """ |
209 Public method to stop the widget. | 222 Public method to stop the widget. |
210 """ | 223 """ |
211 pass | 224 self.__viewmanager.editorCountChanged.disconnect(self.__updateSaveButtonStates) |
225 | |
226 @pyqtSlot() | |
227 def __updateSaveButtonStates(self): | |
228 """ | |
229 Private slot to update the enabled state of the save buttons. | |
230 """ | |
231 enable = bool(len(self.deviceFileTreeWidget.selectedItems())) | |
232 if enable: | |
233 enable &= not ( | |
234 self.deviceFileTreeWidget.selectedItems()[0].text(0).endswith("/") | |
235 ) | |
236 editorsCount = self.__viewmanager.getOpenEditorsCount() | |
237 | |
238 self.saveButton.setEnabled(enable and bool(editorsCount)) | |
239 self.saveAsButton.setEnabled(bool(editorsCount)) | |
212 | 240 |
213 @pyqtSlot(str, str) | 241 @pyqtSlot(str, str) |
214 def __handleError(self, method, error): | 242 def __handleError(self, method, error): |
215 """ | 243 """ |
216 Private slot to handle errors. | 244 Private slot to handle errors. |
238 @type str | 266 @type str |
239 """ | 267 """ |
240 self.deviceCwd.setText(dirname) | 268 self.deviceCwd.setText(dirname) |
241 self.__newDeviceList() | 269 self.__newDeviceList() |
242 | 270 |
271 def __findDirectoryItem(self, dirPath, fileTreeWidget): | |
272 """ | |
273 Private method to find a file tree item for the given path. | |
274 | |
275 @param dirPath path to be searched for | |
276 @type str | |
277 @param fileTreeWidget reference to the file list to be searched | |
278 @type QTreeWidget | |
279 @return reference to the item for the path | |
280 @rtype QTreeWidgetItem | |
281 """ | |
282 itm = fileTreeWidget.topLevelItem(0) | |
283 while itm is not None: | |
284 if itm.data(0, Qt.ItemDataRole.UserRole) == dirPath: | |
285 return itm | |
286 itm = fileTreeWidget.itemBelow(itm) | |
287 | |
288 return None | |
289 | |
243 @pyqtSlot(tuple) | 290 @pyqtSlot(tuple) |
244 def __handleLongListFiles(self, filesList): | 291 def __handleLongListFiles(self, filesList): |
245 """ | 292 """ |
246 Private slot to receive a long directory listing. | 293 Private slot to receive a long directory listing. |
247 | 294 |
248 @param filesList tuple containing tuples with name, mode, size and time | 295 @param filesList tuple containing tuples with name, mode, size and time |
249 for each directory entry | 296 for each directory entry |
250 @type tuple of (str, str, str, str) | 297 @type tuple of (str, str, str, str) |
251 """ | 298 """ |
252 self.deviceFileTreeWidget.clear() | 299 if filesList: |
253 for name, mode, size, dateTime in filesList: | 300 dirPath = os.path.dirname(filesList[0][-1]) |
254 itm = QTreeWidgetItem( | 301 dirItem = ( |
255 self.deviceFileTreeWidget, [name, mode, size, dateTime] | 302 self.__findDirectoryItem(dirPath, self.deviceFileTreeWidget) |
256 ) | 303 if dirPath != self.deviceCwd.text() |
257 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignHCenter) | 304 else None |
258 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) | 305 ) |
306 | |
307 if dirItem: | |
308 dirItem.takeChildren() | |
309 else: | |
310 self.deviceFileTreeWidget.clear() | |
311 | |
312 for name, mode, size, dateTime, filePath in filesList: | |
313 itm = QTreeWidgetItem( | |
314 self.deviceFileTreeWidget if dirItem is None else dirItem, | |
315 [name, mode, size, dateTime], | |
316 ) | |
317 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignHCenter) | |
318 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) | |
319 itm.setData(0, Qt.ItemDataRole.UserRole, filePath) | |
320 if name.endswith("/"): | |
321 itm.setChildIndicatorPolicy( | |
322 QTreeWidgetItem.ChildIndicatorPolicy.ShowIndicator | |
323 ) | |
259 self.deviceFileTreeWidget.header().resizeSections( | 324 self.deviceFileTreeWidget.header().resizeSections( |
260 QHeaderView.ResizeMode.ResizeToContents | 325 QHeaderView.ResizeMode.ResizeToContents |
261 ) | 326 ) |
262 | 327 |
263 def __listLocalFiles(self, dirname="", localDevice=False): | 328 if self.__expandedDeviceEntries: |
329 dirPath = self.__expandedDeviceEntries.pop(0) | |
330 dirItem = self.__findDirectoryItem(dirPath, self.deviceFileTreeWidget) | |
331 if dirItem: | |
332 dirItem.setExpanded(True) | |
333 | |
334 def __listLocalFiles(self, dirname="", localDevice=False, parentItem=None): | |
264 """ | 335 """ |
265 Private method to populate the local files list. | 336 Private method to populate the local files list. |
266 | 337 |
267 @param dirname name of the local directory to be listed | 338 @param dirname name of the local directory to be listed (defaults to "") |
268 @type str | 339 @type str (optional) |
269 @param localDevice flag indicating device access via local file system | 340 @param localDevice flag indicating device access via local file system |
270 @type bool | 341 (defaults to False) |
271 """ | 342 @type bool (optional) |
272 if not dirname: | 343 @param parentItem reference to the parent item (defaults to None) |
273 dirname = os.getcwd() | 344 @type QTreeWidgetItem (optional) |
274 if dirname != os.sep and dirname.endswith(os.sep): | 345 """ |
275 dirname = dirname[:-1] | 346 if parentItem: |
276 if localDevice: | 347 dirname = parentItem.data(0, Qt.ItemDataRole.UserRole) |
277 self.deviceCwd.setText(dirname) | 348 showHidden = ( |
278 showHidden = Preferences.getMicroPython("ShowHiddenDevice") | 349 Preferences.getMicroPython("ShowHiddenDevice") |
279 else: | 350 if localDevice |
280 self.localCwd.setText(dirname) | 351 else Preferences.getMicroPython("ShowHiddenLocal") |
281 showHidden = Preferences.getMicroPython("ShowHiddenLocal") | 352 ) |
353 else: | |
354 if not dirname: | |
355 dirname = os.getcwd() | |
356 if dirname != os.sep and dirname.endswith(os.sep): | |
357 dirname = dirname[:-1] | |
358 if localDevice: | |
359 self.deviceCwd.setText(dirname) | |
360 showHidden = Preferences.getMicroPython("ShowHiddenDevice") | |
361 else: | |
362 self.localCwd.setText(dirname) | |
363 showHidden = Preferences.getMicroPython("ShowHiddenLocal") | |
282 | 364 |
283 filesStatList = listdirStat(dirname, showHidden=showHidden) | 365 filesStatList = listdirStat(dirname, showHidden=showHidden) |
284 filesList = [ | 366 filesList = [ |
285 ( | 367 ( |
286 decoratedName(f, s[0], os.path.isdir(os.path.join(dirname, f))), | 368 decoratedName(f, s[0], os.path.isdir(os.path.join(dirname, f))), |
287 mode2string(s[0]), | 369 mode2string(s[0]), |
288 str(s[6]), | 370 str(s[6]), |
289 mtime2string(s[8]), | 371 mtime2string(s[8]), |
372 os.path.join(dirname, f), | |
290 ) | 373 ) |
291 for f, s in filesStatList | 374 for f, s in filesStatList |
292 ] | 375 ] |
293 fileTreeWidget = ( | 376 fileTreeWidget = ( |
294 self.deviceFileTreeWidget if localDevice else self.localFileTreeWidget | 377 self.deviceFileTreeWidget if localDevice else self.localFileTreeWidget |
295 ) | 378 ) |
296 fileTreeWidget.clear() | 379 if parentItem: |
380 parentItem.takeChildren() | |
381 else: | |
382 fileTreeWidget.clear() | |
383 parentItem = fileTreeWidget | |
297 for item in filesList: | 384 for item in filesList: |
298 itm = QTreeWidgetItem(fileTreeWidget, item) | 385 itm = QTreeWidgetItem(parentItem, item[:4]) |
299 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignHCenter) | 386 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignHCenter) |
300 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) | 387 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) |
388 itm.setData(0, Qt.ItemDataRole.UserRole, item[4]) | |
389 if os.path.isdir(item[4]): | |
390 itm.setChildIndicatorPolicy( | |
391 QTreeWidgetItem.ChildIndicatorPolicy.ShowIndicator | |
392 ) | |
301 fileTreeWidget.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) | 393 fileTreeWidget.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) |
394 | |
395 def __repopulateLocalFilesList(self, dirname="", localDevice=False): | |
396 """ | |
397 Private method to re-populate the local files tree. | |
398 | |
399 @param dirname name of the local directory to be listed (defaults to "") | |
400 @type str (optional) | |
401 @param localDevice flag indicating device access via local file system | |
402 (defaults to False) | |
403 @type bool (optional) | |
404 """ | |
405 fileTreeWidget = ( | |
406 self.deviceFileTreeWidget if localDevice else self.localFileTreeWidget | |
407 ) | |
408 | |
409 # Step 1: record all expanded directories | |
410 expanded = [] | |
411 itm = fileTreeWidget.topLevelItem(0) | |
412 while itm: | |
413 if itm.isExpanded(): | |
414 expanded.append(itm.data(0, Qt.ItemDataRole.UserRole)) | |
415 itm = fileTreeWidget.itemBelow(itm) | |
416 | |
417 # Step 2: re-populate the top level directory | |
418 self.__listLocalFiles(dirname=dirname, localDevice=localDevice) | |
419 | |
420 # Step 3: re-populate expanded directories | |
421 itm = fileTreeWidget.topLevelItem(0) | |
422 while itm: | |
423 if itm.data(0, Qt.ItemDataRole.UserRole) in expanded: | |
424 itm.setExpanded(True) | |
425 itm = fileTreeWidget.itemBelow(itm) | |
302 | 426 |
303 @pyqtSlot(QTreeWidgetItem, int) | 427 @pyqtSlot(QTreeWidgetItem, int) |
304 def on_localFileTreeWidget_itemActivated(self, item, column): | 428 def on_localFileTreeWidget_itemActivated(self, item, column): |
305 """ | 429 """ |
306 Private slot to handle the activation of a local item. | 430 Private slot to handle the activation of a local item. |
311 @param item reference to the activated item | 435 @param item reference to the activated item |
312 @type QTreeWidgetItem | 436 @type QTreeWidgetItem |
313 @param column column of the activation | 437 @param column column of the activation |
314 @type int | 438 @type int |
315 """ | 439 """ |
316 name = os.path.join(self.localCwd.text(), item.text(0)) | 440 name = item.data(0, Qt.ItemDataRole.UserRole) |
317 if name.endswith("/"): | 441 if item.text(0).endswith("/"): |
318 # directory names end with a '/' | 442 # directory names end with a '/' |
319 self.__listLocalFiles(name[:-1]) | 443 self.__listLocalFiles(dirname=name) |
320 elif MimeTypes.isTextFile(name): | 444 elif MimeTypes.isTextFile(name): |
321 ericApp().getObject("ViewManager").getEditor(name) | 445 self.__viewmanager.getEditor(name) |
322 | 446 |
323 @pyqtSlot() | 447 @pyqtSlot() |
324 def on_localFileTreeWidget_itemSelectionChanged(self): | 448 def on_localFileTreeWidget_itemSelectionChanged(self): |
325 """ | 449 """ |
326 Private slot handling a change of selection in the local pane. | 450 Private slot handling a change of selection in the local pane. |
327 """ | 451 """ |
328 enable = bool(len(self.localFileTreeWidget.selectedItems())) | 452 enable = bool(self.localFileTreeWidget.selectedItems()) |
453 self.__localClearSelectionAct.setEnabled(enable) | |
454 | |
329 if enable: | 455 if enable: |
330 enable &= not ( | 456 enable &= not ( |
331 self.localFileTreeWidget.selectedItems()[0].text(0).endswith("/") | 457 self.localFileTreeWidget.selectedItems()[0].text(0).endswith("/") |
332 ) | 458 ) |
333 self.putButton.setEnabled(enable) | 459 self.putButton.setEnabled(enable) |
334 self.putAsButton.setEnabled(enable) | 460 self.putAsButton.setEnabled(enable) |
335 | 461 |
462 @pyqtSlot(QTreeWidgetItem) | |
463 def on_localFileTreeWidget_itemExpanded(self, item): | |
464 """ | |
465 Private slot handling the expansion of a local directory item. | |
466 | |
467 @param item reference to the directory item | |
468 @type QTreeWidgetItem | |
469 """ | |
470 if item.childCount() == 0: | |
471 # it was not populated yet | |
472 self.__listLocalFiles(parentItem=item) | |
473 | |
336 @pyqtSlot(str) | 474 @pyqtSlot(str) |
337 def on_localCwd_textChanged(self, cwd): | 475 def on_localCwd_textChanged(self, cwd): |
338 """ | 476 """ |
339 Private slot handling a change of the current local working directory. | 477 Private slot handling a change of the current local working directory. |
340 | 478 |
348 """ | 486 """ |
349 Private slot to go up one directory level. | 487 Private slot to go up one directory level. |
350 """ | 488 """ |
351 cwd = self.localCwd.text() | 489 cwd = self.localCwd.text() |
352 dirname = os.path.dirname(cwd) | 490 dirname = os.path.dirname(cwd) |
353 self.__listLocalFiles(dirname) | 491 self.__listLocalFiles(dirname=dirname) |
354 | 492 |
355 @pyqtSlot() | 493 @pyqtSlot() |
356 def on_localHomeButton_clicked(self): | 494 def on_localHomeButton_clicked(self): |
357 """ | 495 """ |
358 Private slot to change directory to the configured workspace. | 496 Private slot to change directory to the configured workspace. |
360 dirname = ( | 498 dirname = ( |
361 Preferences.getMicroPython("MpyWorkspace") | 499 Preferences.getMicroPython("MpyWorkspace") |
362 or Preferences.getMultiProject("Workspace") | 500 or Preferences.getMultiProject("Workspace") |
363 or os.path.expanduser("~") | 501 or os.path.expanduser("~") |
364 ) | 502 ) |
365 self.__listLocalFiles(dirname) | 503 self.__listLocalFiles(dirname=dirname) |
366 | 504 |
367 @pyqtSlot() | 505 @pyqtSlot() |
368 def on_localReloadButton_clicked(self): | 506 def on_localReloadButton_clicked(self): |
369 """ | 507 """ |
370 Private slot to reload the local list. | 508 Private slot to reload the local list. |
371 """ | 509 """ |
372 dirname = self.localCwd.text() | 510 dirname = self.localCwd.text() |
373 self.__listLocalFiles(dirname) | 511 self.__repopulateLocalFilesList(dirname=dirname) |
374 | 512 |
375 @pyqtSlot(QTreeWidgetItem, int) | 513 @pyqtSlot(QTreeWidgetItem, int) |
376 def on_deviceFileTreeWidget_itemActivated(self, item, column): | 514 def on_deviceFileTreeWidget_itemActivated(self, item, column): |
377 """ | 515 """ |
378 Private slot to handle the activation of a device item. | 516 Private slot to handle the activation of a device item. |
383 @param item reference to the activated item | 521 @param item reference to the activated item |
384 @type QTreeWidgetItem | 522 @type QTreeWidgetItem |
385 @param column column of the activation | 523 @param column column of the activation |
386 @type int | 524 @type int |
387 """ | 525 """ |
526 name = item.data(0, Qt.ItemDataRole.UserRole) | |
388 if self.__repl.deviceSupportsLocalFileAccess(): | 527 if self.__repl.deviceSupportsLocalFileAccess(): |
389 name = os.path.join(self.deviceCwd.text(), item.text(0)) | 528 if item.text(0).endswith("/"): |
390 if name.endswith("/"): | |
391 # directory names end with a '/' | 529 # directory names end with a '/' |
392 self.__listLocalFiles(name[:-1], True) | 530 self.__listLocalFiles(dirname=name) |
393 else: | 531 else: |
394 if not os.path.exists(name): | 532 if not os.path.exists(name): |
395 EricMessageBox.warning( | 533 EricMessageBox.warning( |
396 self, | 534 self, |
397 self.tr("Open Device File"), | 535 self.tr("Open Device File"), |
399 """<p>The file <b>{0}</b> does not exist.</p>""" | 537 """<p>The file <b>{0}</b> does not exist.</p>""" |
400 ).format(name), | 538 ).format(name), |
401 ) | 539 ) |
402 return | 540 return |
403 if MimeTypes.isTextFile(name): | 541 if MimeTypes.isTextFile(name): |
404 ericApp().getObject("ViewManager").getEditor(name) | 542 self.__viewmanager.getEditor(name) |
405 else: | 543 else: |
406 cwd = self.deviceCwd.text() | 544 if item.text(0).endswith("/"): |
407 if cwd: | |
408 name = ( | |
409 cwd + item.text(0) | |
410 if cwd.endswith("/") | |
411 else "{0}/{1}".format(cwd, item.text(0)) | |
412 ) | |
413 else: | |
414 name = item.text(0) | |
415 if name.endswith("/"): | |
416 # directory names end with a '/' | 545 # directory names end with a '/' |
417 self.__fileManager.cd(name[:-1]) | 546 self.__fileManager.cd(name) |
418 else: | 547 else: |
419 data = self.__fileManager.getData(name) | 548 data = self.__fileManager.getData(name) |
420 try: | 549 try: |
421 text = data.decode(encoding="utf-8") | 550 text = data.decode(encoding="utf-8") |
422 ericApp().getObject("ViewManager").newEditorWithText( | 551 self.__viewmanager.newEditorWithText( |
423 text, fileName=FileSystemUtilities.deviceFileName(name) | 552 text, fileName=FileSystemUtilities.deviceFileName(name) |
424 ) | 553 ) |
425 except UnicodeDecodeError: | 554 except UnicodeDecodeError: |
426 EricMessageBox.warning( | 555 EricMessageBox.warning( |
427 self, | 556 self, |
435 @pyqtSlot() | 564 @pyqtSlot() |
436 def on_deviceFileTreeWidget_itemSelectionChanged(self): | 565 def on_deviceFileTreeWidget_itemSelectionChanged(self): |
437 """ | 566 """ |
438 Private slot handling a change of selection in the local pane. | 567 Private slot handling a change of selection in the local pane. |
439 """ | 568 """ |
440 enable = bool(len(self.deviceFileTreeWidget.selectedItems())) | 569 enable = bool(self.deviceFileTreeWidget.selectedItems()) |
570 self.__deviceClearSelectionAct.setEnabled(enable) | |
571 | |
441 if enable: | 572 if enable: |
442 enable &= not ( | 573 enable &= not ( |
443 self.deviceFileTreeWidget.selectedItems()[0].text(0).endswith("/") | 574 self.deviceFileTreeWidget.selectedItems()[0].text(0).endswith("/") |
444 ) | 575 ) |
576 | |
445 self.getButton.setEnabled(enable) | 577 self.getButton.setEnabled(enable) |
446 self.getAsButton.setEnabled(enable) | 578 self.getAsButton.setEnabled(enable) |
447 | 579 |
448 self.openButton.setEnabled(enable) | 580 self.openButton.setEnabled(enable) |
449 self.saveButton.setEnabled(enable) | 581 |
582 self.__updateSaveButtonStates() | |
583 | |
584 @pyqtSlot(QTreeWidgetItem) | |
585 def on_deviceFileTreeWidget_itemExpanded(self, item): | |
586 """ | |
587 Private slot handling the expansion of a local directory item. | |
588 | |
589 @param item reference to the directory item | |
590 @type QTreeWidgetItem | |
591 """ | |
592 if item.childCount() == 0: | |
593 # it was not populated yet | |
594 if self.__repl.deviceSupportsLocalFileAccess(): | |
595 self.__listLocalFiles(localDevice=True, parentItem=item) | |
596 else: | |
597 self.__fileManager.lls( | |
598 item.data(0, Qt.ItemDataRole.UserRole), | |
599 showHidden=Preferences.getMicroPython("ShowHiddenDevice"), | |
600 ) | |
450 | 601 |
451 @pyqtSlot(str) | 602 @pyqtSlot(str) |
452 def on_deviceCwd_textChanged(self, cwd): | 603 def on_deviceCwd_textChanged(self, cwd): |
453 """ | 604 """ |
454 Private slot handling a change of the current device working directory. | 605 Private slot handling a change of the current device working directory. |
464 Private slot to go up one directory level on the device. | 615 Private slot to go up one directory level on the device. |
465 """ | 616 """ |
466 cwd = self.deviceCwd.text() | 617 cwd = self.deviceCwd.text() |
467 dirname = os.path.dirname(cwd) | 618 dirname = os.path.dirname(cwd) |
468 if self.__repl.deviceSupportsLocalFileAccess(): | 619 if self.__repl.deviceSupportsLocalFileAccess(): |
469 self.__listLocalFiles(dirname, True) | 620 self.__listLocalFiles(dirname=dirname, localDevice=True) |
470 else: | 621 else: |
471 self.__fileManager.cd(dirname) | 622 self.__fileManager.cd(dirname) |
472 | 623 |
473 @pyqtSlot() | 624 @pyqtSlot() |
474 def on_deviceHomeButton_clicked(self): | 625 def on_deviceHomeButton_clicked(self): |
476 Private slot to move to the device home directory. | 627 Private slot to move to the device home directory. |
477 """ | 628 """ |
478 if self.__repl.deviceSupportsLocalFileAccess(): | 629 if self.__repl.deviceSupportsLocalFileAccess(): |
479 dirname = self.__repl.getDeviceWorkspace() | 630 dirname = self.__repl.getDeviceWorkspace() |
480 if dirname: | 631 if dirname: |
481 self.__listLocalFiles(dirname, True) | 632 self.__listLocalFiles(dirname=dirname, localDevice=True) |
482 return | 633 return |
483 | 634 |
484 # list files via device script | 635 # list files via device script |
485 self.__fileManager.cd("/") | 636 self.__fileManager.cd("/") |
486 | 637 |
489 """ | 640 """ |
490 Private slot to reload the device list. | 641 Private slot to reload the device list. |
491 """ | 642 """ |
492 dirname = self.deviceCwd.text() | 643 dirname = self.deviceCwd.text() |
493 if self.__repl.deviceSupportsLocalFileAccess(): | 644 if self.__repl.deviceSupportsLocalFileAccess(): |
494 self.__listLocalFiles(dirname, True) | 645 self.__repopulateLocalFilesList(dirname=dirname, localDevice=True) |
495 else: | 646 else: |
496 if dirname: | 647 if dirname: |
497 self.__newDeviceList() | 648 self.__newDeviceList() |
498 else: | 649 else: |
499 self.__fileManager.pwd() | 650 self.__fileManager.pwd() |
500 | 651 |
501 def __isFileInList(self, filename, treeWidget): | 652 def __isFileInList(self, filename, parent): |
502 """ | 653 """ |
503 Private method to check, if a file name is contained in a tree widget. | 654 Private method to check, if a file name is contained in a tree widget. |
504 | 655 |
505 @param filename name of the file to check | 656 @param filename name of the file to check |
506 @type str | 657 @type str |
507 @param treeWidget reference to the tree widget to be checked against | 658 @param parent reference to the parent to be checked against |
508 @type QTreeWidget | 659 @type QTreeWidget or QTreeWidgetItem |
509 @return flag indicating that the file name is present | 660 @return flag indicating that the file name is present |
510 @rtype bool | 661 @rtype bool |
511 """ | 662 """ |
512 itemCount = treeWidget.topLevelItemCount() | 663 if isinstance(parent, QTreeWidgetItem): |
513 return itemCount > 0 and any( | 664 itemCount = parent.childCount() |
514 treeWidget.topLevelItem(row).text(0) == filename for row in range(itemCount) | 665 return itemCount > 0 and any( |
515 ) | 666 parent.child(row).text(0) == filename for row in range(itemCount) |
667 ) | |
668 else: | |
669 itemCount = parent.topLevelItemCount() | |
670 return itemCount > 0 and any( | |
671 parent.topLevelItem(row).text(0) == filename for row in range(itemCount) | |
672 ) | |
516 | 673 |
517 @pyqtSlot() | 674 @pyqtSlot() |
518 def on_putButton_clicked(self, putAs=False): | 675 def on_putButton_clicked(self, putAs=False): |
519 """ | 676 """ |
520 Private slot to copy the selected file to the connected device. | 677 Private slot to copy the selected file to the connected device. |
522 @param putAs flag indicating to give it a new name | 679 @param putAs flag indicating to give it a new name |
523 @type bool | 680 @type bool |
524 """ | 681 """ |
525 selectedItems = self.localFileTreeWidget.selectedItems() | 682 selectedItems = self.localFileTreeWidget.selectedItems() |
526 if selectedItems: | 683 if selectedItems: |
527 filename = selectedItems[0].text(0).strip() | 684 filepath = selectedItems[0].data(0, Qt.ItemDataRole.UserRole) |
528 if not filename.endswith("/"): | 685 filename = os.path.basename(filepath) |
686 if not selectedItems[0].text(0).endswith("/"): | |
529 # it is really a file | 687 # it is really a file |
530 if putAs: | 688 if putAs: |
531 deviceFilename, ok = QInputDialog.getText( | 689 deviceFilename, ok = QInputDialog.getText( |
532 self, | 690 self, |
533 self.tr("Put File As"), | 691 self.tr("Put File As"), |
534 self.tr("Enter a new name for the file"), | 692 self.tr("Enter a new name for the file"), |
535 QLineEdit.EchoMode.Normal, | 693 QLineEdit.EchoMode.Normal, |
536 filename, | 694 filename, |
537 ) | 695 ) |
538 if not ok or not filename: | 696 if not ok or not deviceFilename: |
539 return | 697 return |
540 else: | 698 else: |
541 deviceFilename = filename | 699 deviceFilename = filename |
542 | 700 |
543 if self.__isFileInList(deviceFilename, self.deviceFileTreeWidget): | 701 selectedDeviceItems = self.deviceFileTreeWidget.selectedItems() |
702 if selectedDeviceItems: | |
703 item = selectedDeviceItems[0] | |
704 if not item.text(0).endswith("/"): | |
705 # it is no directory, take its parent | |
706 item = item.parent() | |
707 devicePath = ( | |
708 self.deviceCwd.text() | |
709 if item is None | |
710 else item.data(0, Qt.ItemDataRole.UserRole) | |
711 ) | |
712 deviceParent = item | |
713 else: | |
714 devicePath = self.deviceCwd.text() | |
715 deviceParent = self.deviceFileTreeWidget | |
716 | |
717 if self.__isFileInList(deviceFilename, deviceParent): | |
544 # ask for overwrite permission | 718 # ask for overwrite permission |
545 action, resultFilename = confirmOverwrite( | 719 action, resultFilename = confirmOverwrite( |
546 deviceFilename, | 720 deviceFilename, |
547 self.tr("Copy File to Device"), | 721 self.tr("Copy File to Device"), |
548 self.tr( | 722 self.tr( |
555 return | 729 return |
556 elif action == "rename": | 730 elif action == "rename": |
557 deviceFilename = os.path.basename(resultFilename) | 731 deviceFilename = os.path.basename(resultFilename) |
558 | 732 |
559 if self.__repl.deviceSupportsLocalFileAccess(): | 733 if self.__repl.deviceSupportsLocalFileAccess(): |
560 shutil.copy2( | 734 shutil.copy2(filepath, os.path.join(devicePath, deviceFilename)) |
561 os.path.join(self.localCwd.text(), filename), | 735 self.__listLocalFiles(dirname=devicePath, localDevice=True) |
562 os.path.join(self.deviceCwd.text(), deviceFilename), | |
563 ) | |
564 self.__listLocalFiles(self.deviceCwd.text(), localDevice=True) | |
565 else: | 736 else: |
566 deviceCwd = self.deviceCwd.text() | 737 if devicePath: |
567 if deviceCwd: | 738 deviceFilename = ( |
568 if deviceCwd != "/": | 739 f"{devicePath}/{deviceFilename}" |
569 deviceFilename = deviceCwd + "/" + deviceFilename | 740 if devicePath != "/" |
570 else: | 741 else f"/{devicePath}" |
571 deviceFilename = "/" + deviceFilename | 742 ) |
572 self.__fileManager.put( | 743 self.__fileManager.put(filepath, deviceFilename) |
573 os.path.join(self.localCwd.text(), filename), deviceFilename | |
574 ) | |
575 | 744 |
576 @pyqtSlot() | 745 @pyqtSlot() |
577 def on_putAsButton_clicked(self): | 746 def on_putAsButton_clicked(self): |
578 """ | 747 """ |
579 Private slot to copy the selected file to the connected device | 748 Private slot to copy the selected file to the connected device |
590 @type bool | 759 @type bool |
591 """ | 760 """ |
592 selectedItems = self.deviceFileTreeWidget.selectedItems() | 761 selectedItems = self.deviceFileTreeWidget.selectedItems() |
593 if selectedItems: | 762 if selectedItems: |
594 filename = selectedItems[0].text(0).strip() | 763 filename = selectedItems[0].text(0).strip() |
764 deviceFilename = selectedItems[0].data(0, Qt.ItemDataRole.UserRole) | |
595 if not filename.endswith("/"): | 765 if not filename.endswith("/"): |
596 # it is really a file | 766 # it is really a file |
597 if getAs: | 767 if getAs: |
598 localFilename, ok = QInputDialog.getText( | 768 localFilename, ok = QInputDialog.getText( |
599 self, | 769 self, |
605 if not ok or not filename: | 775 if not ok or not filename: |
606 return | 776 return |
607 else: | 777 else: |
608 localFilename = filename | 778 localFilename = filename |
609 | 779 |
610 if self.__isFileInList(localFilename, self.localFileTreeWidget): | 780 selectedLocalItems = self.localFileTreeWidget.selectedItems() |
781 if selectedLocalItems: | |
782 item = selectedLocalItems[0] | |
783 if not item.text(0).endswith("/"): | |
784 # it is no directory, take its parent | |
785 item = item.parent() | |
786 localPath = ( | |
787 self.localCwd.text() | |
788 if item is None | |
789 else item.data(0, Qt.ItemDataRole.UserRole) | |
790 ) | |
791 localParent = item | |
792 else: | |
793 localPath = self.localCwd.text() | |
794 localParent = self.localFileTreeWidget | |
795 | |
796 if self.__isFileInList(localFilename, localParent): | |
611 # ask for overwrite permission | 797 # ask for overwrite permission |
612 action, resultFilename = confirmOverwrite( | 798 action, resultFilename = confirmOverwrite( |
613 localFilename, | 799 localFilename, |
614 self.tr("Copy File from Device"), | 800 self.tr("Copy File from Device"), |
615 self.tr("The given file exists already."), | 801 self.tr("The given file exists already."), |
621 elif action == "rename": | 807 elif action == "rename": |
622 localFilename = resultFilename | 808 localFilename = resultFilename |
623 | 809 |
624 if self.__repl.deviceSupportsLocalFileAccess(): | 810 if self.__repl.deviceSupportsLocalFileAccess(): |
625 shutil.copy2( | 811 shutil.copy2( |
626 os.path.join(self.deviceCwd.text(), filename), | 812 deviceFilename, |
627 os.path.join(self.localCwd.text(), localFilename), | 813 os.path.join(localPath, localFilename), |
628 ) | 814 ) |
629 self.__listLocalFiles(self.localCwd.text()) | 815 if isinstance(localParent, QTreeWidgetItem): |
816 self.__listLocalFiles(parentItem=localParent) | |
817 else: | |
818 self.__listLocalFiles(dirname=localPath) | |
630 else: | 819 else: |
631 deviceCwd = self.deviceCwd.text() | |
632 if deviceCwd: | |
633 filename = deviceCwd + "/" + filename | |
634 self.__fileManager.get( | 820 self.__fileManager.get( |
635 filename, os.path.join(self.localCwd.text(), localFilename) | 821 deviceFilename, os.path.join(localPath, localFilename) |
636 ) | 822 ) |
637 | 823 |
638 @pyqtSlot() | 824 @pyqtSlot() |
639 def on_getAsButton_clicked(self): | 825 def on_getAsButton_clicked(self): |
640 """ | 826 """ |
651 @param deviceFile name of the file on the device | 837 @param deviceFile name of the file on the device |
652 @type str | 838 @type str |
653 @param localFile name of the local file | 839 @param localFile name of the local file |
654 @type str | 840 @type str |
655 """ | 841 """ |
656 self.__listLocalFiles(self.localCwd.text()) | 842 localPath = os.path.dirname(localFile) |
843 | |
844 # find the directory entry associated with the new file | |
845 localParent = self.__findDirectoryItem(localPath, self.localFileTreeWidget) | |
846 | |
847 if localParent: | |
848 self.__listLocalFiles(parentItem=localParent) | |
849 else: | |
850 self.__listLocalFiles(dirname=self.localCwd.text()) | |
657 | 851 |
658 @pyqtSlot() | 852 @pyqtSlot() |
659 def on_syncButton_clicked(self): | 853 def on_syncButton_clicked(self): |
660 """ | 854 """ |
661 Private slot to synchronize the local directory to the device. | 855 Private slot to synchronize the local directory to the device. |
662 """ | 856 """ |
857 # 1. local directory | |
858 selectedItems = self.localFileTreeWidget.selectedItems() | |
859 if selectedItems: | |
860 localName = selectedItems[0].text(0) | |
861 if localName.endswith("/"): | |
862 localDirPath = selectedItems[0].data(0, Qt.ItemDataRole.UserRole) | |
863 else: | |
864 # it is not a directory | |
865 localDirPath = os.path.dirname( | |
866 selectedItems[0].data(0, Qt.ItemDataRole.UserRole) | |
867 ) | |
868 else: | |
869 localName = "" | |
870 localDirPath = self.localCwd.text() | |
871 | |
872 # 2. device directory | |
873 selectedItems = self.deviceFileTreeWidget.selectedItems() | |
874 if selectedItems: | |
875 if not selectedItems[0].text(0).endswith("/"): | |
876 # it is not a directory | |
877 deviceDirPath = os.path.dirname( | |
878 selectedItems[0].data(0, Qt.ItemDataRole.UserRole) | |
879 ) | |
880 else: | |
881 deviceDirPath = selectedItems[0].data(0, Qt.ItemDataRole.UserRole) | |
882 else: | |
883 if localDirPath == self.localCwd.text(): | |
884 # syncronize complete local directory | |
885 deviceDirPath = self.deviceCwd.text() | |
886 else: | |
887 deviceCwd = self.deviceCwd.text() | |
888 deviceDirPath = ( | |
889 f"{deviceCwd}{localName[:-1]}" | |
890 if deviceCwd.endswith("/") | |
891 else f"{deviceCwd}/{localName[:-1]}" | |
892 ) | |
893 | |
663 self.__fileManager.rsync( | 894 self.__fileManager.rsync( |
664 self.localCwd.text(), | 895 localDirPath, |
665 self.deviceCwd.text(), | 896 deviceDirPath, |
666 mirror=True, | 897 mirror=True, |
667 localDevice=self.__repl.deviceSupportsLocalFileAccess(), | 898 localDevice=self.__repl.deviceSupportsLocalFileAccess(), |
668 ) | 899 ) |
669 | 900 |
670 @pyqtSlot(str, str) | 901 @pyqtSlot(str, str) |
710 @pyqtSlot() | 941 @pyqtSlot() |
711 def __newDeviceList(self): | 942 def __newDeviceList(self): |
712 """ | 943 """ |
713 Private slot to initiate a new long list of the device directory. | 944 Private slot to initiate a new long list of the device directory. |
714 """ | 945 """ |
946 self.__expandedDeviceEntries.clear() | |
947 itm = self.deviceFileTreeWidget.topLevelItem(0) | |
948 while itm: | |
949 if itm.isExpanded(): | |
950 self.__expandedDeviceEntries.append( | |
951 itm.data(0, Qt.ItemDataRole.UserRole) | |
952 ) | |
953 itm = self.deviceFileTreeWidget.itemBelow(itm) | |
954 | |
715 self.__fileManager.lls( | 955 self.__fileManager.lls( |
716 self.deviceCwd.text(), | 956 self.deviceCwd.text(), |
717 showHidden=Preferences.getMicroPython("ShowHiddenDevice"), | 957 showHidden=Preferences.getMicroPython("ShowHiddenDevice"), |
718 ) | 958 ) |
719 | 959 |
722 """ | 962 """ |
723 Private slot to open the selected file in a new editor. | 963 Private slot to open the selected file in a new editor. |
724 """ | 964 """ |
725 selectedItems = self.deviceFileTreeWidget.selectedItems() | 965 selectedItems = self.deviceFileTreeWidget.selectedItems() |
726 if selectedItems: | 966 if selectedItems: |
727 filename = selectedItems[0].text(0).strip() | 967 name = selectedItems[0].data(0, Qt.ItemDataRole.UserRole) |
728 if self.__repl.deviceSupportsLocalFileAccess(): | 968 if self.__repl.deviceSupportsLocalFileAccess(): |
729 name = os.path.join(self.deviceCwd.text(), filename) | 969 if not selectedItems[0].text(0).endswith("/") and MimeTypes.isTextFile( |
730 if not name.endswith("/") and MimeTypes.isTextFile(name): | 970 name |
731 ericApp().getObject("ViewManager").getEditor(name) | 971 ): |
972 self.__viewmanager.getEditor(name) | |
732 else: | 973 else: |
733 cwd = self.deviceCwd.text() | 974 if not selectedItems[0].text(0).endswith("/"): |
734 if cwd: | |
735 name = ( | |
736 cwd + filename | |
737 if cwd.endswith("/") | |
738 else "{0}/{1}".format(cwd, filename) | |
739 ) | |
740 else: | |
741 name = filename | |
742 if not name.endswith("/"): | |
743 data = self.__fileManager.getData(name) | 975 data = self.__fileManager.getData(name) |
744 text = data.decode(encoding="utf-8") | 976 text = data.decode(encoding="utf-8") |
745 ericApp().getObject("ViewManager").newEditorWithText( | 977 self.__viewmanager.newEditorWithText( |
746 text, "Python3", FileSystemUtilities.deviceFileName(name) | 978 text, "Python3", FileSystemUtilities.deviceFileName(name) |
747 ) | 979 ) |
748 | 980 |
749 @pyqtSlot() | 981 @pyqtSlot() |
750 def on_saveButton_clicked(self, saveAs=False): | 982 def on_saveButton_clicked(self, saveAs=False): |
752 Private slot to save the text of the current editor to a file on the device. | 984 Private slot to save the text of the current editor to a file on the device. |
753 | 985 |
754 @param saveAs flag indicating to save the file with a new name | 986 @param saveAs flag indicating to save the file with a new name |
755 @type bool | 987 @type bool |
756 """ | 988 """ |
757 aw = ericApp().getObject("ViewManager").activeWindow() | 989 aw = self.__viewmanager.activeWindow() |
758 if aw: | 990 if aw: |
759 selectedItems = self.deviceFileTreeWidget.selectedItems() | 991 selectedItems = self.deviceFileTreeWidget.selectedItems() |
992 | |
760 if selectedItems: | 993 if selectedItems: |
761 filename = selectedItems[0].text(0).strip() | 994 filepath = selectedItems[0].data(0, Qt.ItemDataRole.UserRole) |
762 if filename.endswith("/"): | 995 filename = os.path.basename(filepath) |
996 if selectedItems[0].text(0).endswith("/"): | |
763 saveAs = True | 997 saveAs = True |
764 else: | 998 else: |
765 saveAs = True | 999 saveAs = True |
766 filename = "" | 1000 filename = "" |
767 | 1001 |
782 FileSystemUtilities.plainFileName(aw.getFileName()) | 1016 FileSystemUtilities.plainFileName(aw.getFileName()) |
783 ) | 1017 ) |
784 if editorFileName != filename: | 1018 if editorFileName != filename: |
785 saveAs = True | 1019 saveAs = True |
786 | 1020 |
787 if saveAs and self.__isFileInList(filename, self.deviceFileTreeWidget): | 1021 if selectedItems: |
1022 item = selectedItems[0] | |
1023 if not item.text(0).endswith("/"): | |
1024 # it is no directory, take its parent | |
1025 item = item.parent() | |
1026 devicePath = ( | |
1027 self.deviceCwd.text() | |
1028 if item is None | |
1029 else item.data(0, Qt.ItemDataRole.UserRole) | |
1030 ) | |
1031 deviceParent = item | |
1032 else: | |
1033 devicePath = self.deviceCwd.text() | |
1034 deviceParent = self.deviceFileTreeWidget | |
1035 | |
1036 if saveAs and self.__isFileInList(filename, deviceParent): | |
788 # ask for overwrite permission | 1037 # ask for overwrite permission |
789 action, resultFilename = confirmOverwrite( | 1038 action, resultFilename = confirmOverwrite( |
790 filename, | 1039 filename, |
791 self.tr("Save File As"), | 1040 self.tr("Save File As"), |
792 self.tr("The given file exists already (Enter file name only)."), | 1041 self.tr("The given file exists already (Enter file name only)."), |
798 elif action == "rename": | 1047 elif action == "rename": |
799 filename = os.path.basename(resultFilename) | 1048 filename = os.path.basename(resultFilename) |
800 | 1049 |
801 text = aw.text() | 1050 text = aw.text() |
802 if self.__repl.deviceSupportsLocalFileAccess(): | 1051 if self.__repl.deviceSupportsLocalFileAccess(): |
803 filename = os.path.join(self.deviceCwd.text(), filename) | 1052 filename = os.path.join(devicePath, filename) |
804 os.makedirs(os.path.dirname(filename), exist_ok=True) | 1053 os.makedirs(os.path.dirname(filename), exist_ok=True) |
805 with open(filename, "w") as f: | 1054 with open(filename, "w") as f: |
806 f.write(text) | 1055 f.write(text) |
807 self.__newDeviceList() | 1056 self.__newDeviceList() |
808 aw.setFileName(filename) | 1057 aw.setFileName(filename) |
809 else: | 1058 else: |
810 if not filename.startswith("/"): | 1059 filename = ( |
811 deviceCwd = self.deviceCwd.text() | 1060 f"{devicePath}/{filename}" |
812 if deviceCwd: | 1061 if devicePath != "/" |
813 filename = ( | 1062 else f"/{devicePath}" |
814 deviceCwd + "/" + filename | 1063 ) |
815 if deviceCwd != "/" | |
816 else "/" + filename | |
817 ) | |
818 dirname = filename.rsplit("/", 1)[0] | 1064 dirname = filename.rsplit("/", 1)[0] |
819 self.__fileManager.makedirs(dirname) | 1065 self.__fileManager.makedirs(dirname) |
820 self.__fileManager.putData(filename, text.encode("utf-8")) | 1066 self.__fileManager.putData(filename, text.encode("utf-8")) |
821 aw.setFileName(FileSystemUtilities.deviceFileName(filename)) | 1067 aw.setFileName(FileSystemUtilities.deviceFileName(filename)) |
822 | 1068 |
823 aw.setModified(False) | 1069 aw.setModified(False) |
824 aw.resetOnlineChangeTraceInfo() | 1070 with contextlib.suppress(AttributeError): |
1071 aw.resetOnlineChangeTraceInfo() | |
825 | 1072 |
826 @pyqtSlot() | 1073 @pyqtSlot() |
827 def on_saveAsButton_clicked(self): | 1074 def on_saveAsButton_clicked(self): |
828 """ | 1075 """ |
829 Private slot to save the current editor in a new file on the connected device. | 1076 Private slot to save the current editor in a new file on the connected device. |
844 """ | 1091 """ |
845 hasSelection = bool(len(self.localFileTreeWidget.selectedItems())) | 1092 hasSelection = bool(len(self.localFileTreeWidget.selectedItems())) |
846 if hasSelection: | 1093 if hasSelection: |
847 name = self.localFileTreeWidget.selectedItems()[0].text(0) | 1094 name = self.localFileTreeWidget.selectedItems()[0].text(0) |
848 isDir = name.endswith("/") | 1095 isDir = name.endswith("/") |
849 isFile = not isDir | 1096 isLink = name.endswith("@") |
1097 isFile = not (isDir or isLink) | |
850 else: | 1098 else: |
851 isDir = False | 1099 isDir = False |
852 isFile = False | 1100 isFile = False |
853 self.__localDelDirTreeAct.setEnabled(isDir) | 1101 self.__localDelDirTreeAct.setEnabled(isDir) |
854 self.__localRenameFileAct.setEnabled(isFile) | 1102 self.__localRenameFileAct.setEnabled(isFile) |
863 | 1111 |
864 @param localDevice flag indicating device access via local file system | 1112 @param localDevice flag indicating device access via local file system |
865 @type bool | 1113 @type bool |
866 """ | 1114 """ |
867 cwdWidget = self.deviceCwd if localDevice else self.localCwd | 1115 cwdWidget = self.deviceCwd if localDevice else self.localCwd |
868 | 1116 fileTreeWidget = ( |
1117 self.deviceFileTreeWidget if localDevice else self.localFileTreeWidget | |
1118 ) | |
1119 | |
1120 if fileTreeWidget.selectedItems(): | |
1121 defaultPath = fileTreeWidget.selectedItems()[0].data( | |
1122 0, Qt.ItemDataRole.UserRole | |
1123 ) | |
1124 if not os.path.isdir(defaultPath): | |
1125 defaultPath = os.path.dirname(defaultPath) | |
1126 else: | |
1127 defaultPath = cwdWidget.text() | |
869 dirPath, ok = EricPathPickerDialog.getStrPath( | 1128 dirPath, ok = EricPathPickerDialog.getStrPath( |
870 self, | 1129 self, |
871 self.tr("Change Directory"), | 1130 self.tr("Change Directory"), |
872 self.tr("Select Directory"), | 1131 self.tr("Select Directory"), |
873 EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE, | 1132 EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE, |
874 strPath=cwdWidget.text(), | 1133 strPath=defaultPath, |
875 defaultDirectory=cwdWidget.text(), | 1134 defaultDirectory=defaultPath, |
876 ) | 1135 ) |
877 if ok and dirPath: | 1136 if ok and dirPath: |
878 if not os.path.isabs(dirPath): | 1137 if not os.path.isabs(dirPath): |
879 dirPath = os.path.join(cwdWidget.text(), dirPath) | 1138 dirPath = os.path.join(cwdWidget.text(), dirPath) |
880 cwdWidget.setText(dirPath) | 1139 cwdWidget.setText(dirPath) |
881 self.__listLocalFiles(dirPath, localDevice=localDevice) | 1140 self.__listLocalFiles(dirname=dirPath, localDevice=localDevice) |
882 | 1141 |
883 @pyqtSlot() | 1142 @pyqtSlot() |
884 def __createLocalDirectory(self, localDevice=False): | 1143 def __createLocalDirectory(self, localDevice=False): |
885 """ | 1144 """ |
886 Private slot to create a local directory. | 1145 Private slot to create a local directory. |
887 | 1146 |
888 @param localDevice flag indicating device access via local file system | 1147 @param localDevice flag indicating device access via local file system |
889 @type bool | 1148 @type bool |
890 """ | 1149 """ |
891 cwdWidget = self.deviceCwd if localDevice else self.localCwd | 1150 cwdWidget = self.deviceCwd if localDevice else self.localCwd |
1151 fileTreeWidget = ( | |
1152 self.deviceFileTreeWidget if localDevice else self.localFileTreeWidget | |
1153 ) | |
1154 | |
1155 if fileTreeWidget.selectedItems(): | |
1156 localItem = fileTreeWidget.selectedItems()[0] | |
1157 defaultPath = localItem.data(0, Qt.ItemDataRole.UserRole) | |
1158 if not os.path.isdir(defaultPath): | |
1159 defaultPath = os.path.dirname(defaultPath) | |
1160 localItem = localItem.parent() | |
1161 else: | |
1162 defaultPath = cwdWidget.text() | |
1163 localItem = None | |
892 | 1164 |
893 dirPath, ok = QInputDialog.getText( | 1165 dirPath, ok = QInputDialog.getText( |
894 self, | 1166 self, |
895 self.tr("Create Directory"), | 1167 self.tr("Create Directory"), |
896 self.tr("Enter directory name:"), | 1168 self.tr("Enter directory name:"), |
897 QLineEdit.EchoMode.Normal, | 1169 QLineEdit.EchoMode.Normal, |
898 ) | 1170 ) |
899 if ok and dirPath: | 1171 if ok and dirPath: |
900 dirPath = os.path.join(cwdWidget.text(), dirPath) | 1172 dirPath = os.path.join(defaultPath, dirPath) |
901 try: | 1173 try: |
902 os.mkdir(dirPath) | 1174 os.mkdir(dirPath) |
903 self.__listLocalFiles(cwdWidget.text(), localDevice=localDevice) | 1175 if localItem: |
1176 self.__listLocalFiles(localDevice=localDevice, parentItem=localItem) | |
1177 else: | |
1178 self.__listLocalFiles( | |
1179 dirname=cwdWidget.text(), localDevice=localDevice | |
1180 ) | |
904 except OSError as exc: | 1181 except OSError as exc: |
905 EricMessageBox.critical( | 1182 EricMessageBox.critical( |
906 self, | 1183 self, |
907 self.tr("Create Directory"), | 1184 self.tr("Create Directory"), |
908 self.tr( | 1185 self.tr( |
924 fileTreeWidget = self.deviceFileTreeWidget | 1201 fileTreeWidget = self.deviceFileTreeWidget |
925 else: | 1202 else: |
926 cwdWidget = self.localCwd | 1203 cwdWidget = self.localCwd |
927 fileTreeWidget = self.localFileTreeWidget | 1204 fileTreeWidget = self.localFileTreeWidget |
928 | 1205 |
929 if bool(len(fileTreeWidget.selectedItems())): | 1206 if bool(fileTreeWidget.selectedItems()): |
930 name = fileTreeWidget.selectedItems()[0].text(0) | 1207 localItem = fileTreeWidget.selectedItems()[0] |
931 dirname = os.path.join(cwdWidget.text(), name[:-1]) | 1208 parentItem = localItem.parent() |
1209 dirname = localItem.data(0, Qt.ItemDataRole.UserRole) | |
932 dlg = DeleteFilesConfirmationDialog( | 1210 dlg = DeleteFilesConfirmationDialog( |
933 self, | 1211 self, |
934 self.tr("Delete Directory Tree"), | 1212 self.tr("Delete Directory Tree"), |
935 self.tr("Do you really want to delete this directory tree?"), | 1213 self.tr("Do you really want to delete this directory tree?"), |
936 [dirname], | 1214 [dirname], |
937 ) | 1215 ) |
938 if dlg.exec() == QDialog.DialogCode.Accepted: | 1216 if dlg.exec() == QDialog.DialogCode.Accepted: |
939 try: | 1217 try: |
940 shutil.rmtree(dirname) | 1218 shutil.rmtree(dirname) |
941 self.__listLocalFiles(cwdWidget.text(), localDevice=localDevice) | 1219 if parentItem: |
1220 self.__listLocalFiles( | |
1221 localDevice=localDevice, parentItem=parentItem | |
1222 ) | |
1223 else: | |
1224 self.__listLocalFiles( | |
1225 dirname=cwdWidget.text(), localDevice=localDevice | |
1226 ) | |
942 except Exception as exc: | 1227 except Exception as exc: |
943 EricMessageBox.critical( | 1228 EricMessageBox.critical( |
944 self, | 1229 self, |
945 self.tr("Delete Directory Tree"), | 1230 self.tr("Delete Directory Tree"), |
946 self.tr( | 1231 self.tr( |
963 else: | 1248 else: |
964 cwdWidget = self.localCwd | 1249 cwdWidget = self.localCwd |
965 fileTreeWidget = self.localFileTreeWidget | 1250 fileTreeWidget = self.localFileTreeWidget |
966 | 1251 |
967 if bool(len(fileTreeWidget.selectedItems())): | 1252 if bool(len(fileTreeWidget.selectedItems())): |
968 name = fileTreeWidget.selectedItems()[0].text(0) | 1253 localItem = fileTreeWidget.selectedItems()[0] |
969 filename = os.path.join(cwdWidget.text(), name) | 1254 parentItem = localItem.parent() |
1255 filename = localItem.data(0, Qt.ItemDataRole.UserRole) | |
970 dlg = DeleteFilesConfirmationDialog( | 1256 dlg = DeleteFilesConfirmationDialog( |
971 self, | 1257 self, |
972 self.tr("Delete File"), | 1258 self.tr("Delete File"), |
973 self.tr("Do you really want to delete this file?"), | 1259 self.tr("Do you really want to delete this file?"), |
974 [filename], | 1260 [filename], |
975 ) | 1261 ) |
976 if dlg.exec() == QDialog.DialogCode.Accepted: | 1262 if dlg.exec() == QDialog.DialogCode.Accepted: |
977 try: | 1263 try: |
978 os.remove(filename) | 1264 os.remove(filename) |
979 self.__listLocalFiles(cwdWidget.text(), localDevice=localDevice) | 1265 if parentItem: |
1266 self.__listLocalFiles( | |
1267 localDevice=localDevice, parentItem=parentItem | |
1268 ) | |
1269 else: | |
1270 self.__listLocalFiles( | |
1271 dirname=cwdWidget.text(), localDevice=localDevice | |
1272 ) | |
980 except OSError as exc: | 1273 except OSError as exc: |
981 EricMessageBox.critical( | 1274 EricMessageBox.critical( |
982 self, | 1275 self, |
983 self.tr("Delete File"), | 1276 self.tr("Delete File"), |
984 self.tr( | 1277 self.tr( |
994 | 1287 |
995 @param localDevice flag indicating device access via local file system | 1288 @param localDevice flag indicating device access via local file system |
996 (defaults to False) | 1289 (defaults to False) |
997 @type bool (optional) | 1290 @type bool (optional) |
998 """ | 1291 """ |
999 if localDevice: | 1292 fileTreeWidget = ( |
1000 cwdWidget = self.deviceCwd | 1293 self.deviceFileTreeWidget if localDevice else self.localFileTreeWidget |
1001 fileTreeWidget = self.deviceFileTreeWidget | 1294 ) |
1002 else: | |
1003 cwdWidget = self.localCwd | |
1004 fileTreeWidget = self.localFileTreeWidget | |
1005 | 1295 |
1006 if bool(len(fileTreeWidget.selectedItems())): | 1296 if bool(len(fileTreeWidget.selectedItems())): |
1007 name = fileTreeWidget.selectedItems()[0].text(0) | 1297 localItem = fileTreeWidget.selectedItems()[0] |
1008 filename = os.path.join(cwdWidget.text(), name) | 1298 filename = localItem.data(0, Qt.ItemDataRole.UserRole) |
1009 newname, ok = QInputDialog.getText( | 1299 newname, ok = QInputDialog.getText( |
1010 self, | 1300 self, |
1011 self.tr("Rename File"), | 1301 self.tr("Rename File"), |
1012 self.tr("Enter the new path for the file"), | 1302 self.tr("Enter the new path for the file"), |
1013 QLineEdit.EchoMode.Normal, | 1303 QLineEdit.EchoMode.Normal, |
1041 @type bool | 1331 @type bool |
1042 """ | 1332 """ |
1043 Preferences.setMicroPython("ShowHiddenLocal", checked) | 1333 Preferences.setMicroPython("ShowHiddenLocal", checked) |
1044 self.on_localReloadButton_clicked() | 1334 self.on_localReloadButton_clicked() |
1045 | 1335 |
1336 @pyqtSlot() | |
1337 def __clearLocalSelection(self): | |
1338 """ | |
1339 Private slot to clear the local selection. | |
1340 """ | |
1341 for item in self.localFileTreeWidget.selectedItems()[:]: | |
1342 item.setSelected(False) | |
1343 | |
1046 ################################################################## | 1344 ################################################################## |
1047 ## Context menu methods for the device files below | 1345 ## Context menu methods for the device files below |
1048 ################################################################## | 1346 ################################################################## |
1049 | 1347 |
1050 @pyqtSlot(QPoint) | 1348 @pyqtSlot(QPoint) |
1080 current directory. | 1378 current directory. |
1081 """ | 1379 """ |
1082 if self.__repl.deviceSupportsLocalFileAccess(): | 1380 if self.__repl.deviceSupportsLocalFileAccess(): |
1083 self.__changeLocalDirectory(True) | 1381 self.__changeLocalDirectory(True) |
1084 else: | 1382 else: |
1383 selectedItems = self.deviceFileTreeWidget.selectedItems() | |
1384 if selectedItems: | |
1385 item = selectedItems[0] | |
1386 dirName = ( | |
1387 item.data(0, Qt.ItemDataRole.UserRole) | |
1388 if item.text(0).endswith("/") | |
1389 else os.path.dirname(item.data(0, Qt.ItemDataRole.UserRole)) | |
1390 ) | |
1391 else: | |
1392 dirName = self.deviceCwd.text() | |
1085 dirPath, ok = QInputDialog.getText( | 1393 dirPath, ok = QInputDialog.getText( |
1086 self, | 1394 self, |
1087 self.tr("Change Directory"), | 1395 self.tr("Change Directory"), |
1088 self.tr("Enter the directory path on the device:"), | 1396 self.tr("Enter the directory path on the device:"), |
1089 QLineEdit.EchoMode.Normal, | 1397 QLineEdit.EchoMode.Normal, |
1090 self.deviceCwd.text(), | 1398 dirName, |
1091 ) | 1399 ) |
1092 if ok and dirPath: | 1400 if ok and dirPath: |
1093 if not dirPath.startswith("/"): | 1401 if not dirPath.startswith("/"): |
1094 dirPath = self.deviceCwd.text() + "/" + dirPath | 1402 dirPath = self.deviceCwd.text() + "/" + dirPath |
1095 self.__fileManager.cd(dirPath) | 1403 self.__fileManager.cd(dirPath) |
1100 Private slot to create a directory on the device. | 1408 Private slot to create a directory on the device. |
1101 """ | 1409 """ |
1102 if self.__repl.deviceSupportsLocalFileAccess(): | 1410 if self.__repl.deviceSupportsLocalFileAccess(): |
1103 self.__createLocalDirectory(True) | 1411 self.__createLocalDirectory(True) |
1104 else: | 1412 else: |
1413 selectedItems = self.deviceFileTreeWidget.selectedItems() | |
1414 if selectedItems: | |
1415 item = selectedItems[0] | |
1416 defaultPath = ( | |
1417 item.data(0, Qt.ItemDataRole.UserRole) | |
1418 if item.text(0).endswith("/") | |
1419 else os.path.dirname(item.data(0, Qt.ItemDataRole.UserRole)) | |
1420 ) | |
1421 else: | |
1422 defaultPath = self.deviceCwd.text() | |
1105 dirPath, ok = QInputDialog.getText( | 1423 dirPath, ok = QInputDialog.getText( |
1106 self, | 1424 self, |
1107 self.tr("Create Directory"), | 1425 self.tr("Create Directory"), |
1108 self.tr("Enter directory name:"), | 1426 self.tr("Enter directory name:"), |
1109 QLineEdit.EchoMode.Normal, | 1427 QLineEdit.EchoMode.Normal, |
1428 defaultPath, | |
1110 ) | 1429 ) |
1111 if ok and dirPath: | 1430 if ok and dirPath: |
1112 self.__fileManager.mkdir(dirPath) | 1431 self.__fileManager.mkdir(dirPath) |
1113 | 1432 |
1114 @pyqtSlot() | 1433 @pyqtSlot() |
1117 Private slot to delete an empty directory on the device. | 1436 Private slot to delete an empty directory on the device. |
1118 """ | 1437 """ |
1119 if self.__repl.deviceSupportsLocalFileAccess(): | 1438 if self.__repl.deviceSupportsLocalFileAccess(): |
1120 self.__deleteLocalDirectoryTree(True) | 1439 self.__deleteLocalDirectoryTree(True) |
1121 else: | 1440 else: |
1122 if bool(len(self.deviceFileTreeWidget.selectedItems())): | 1441 if bool(self.deviceFileTreeWidget.selectedItems()): |
1123 name = self.deviceFileTreeWidget.selectedItems()[0].text(0) | 1442 dirname = self.deviceFileTreeWidget.selectedItems()[0].data( |
1124 cwd = self.deviceCwd.text() | 1443 0, Qt.ItemDataRole.UserRole |
1125 if cwd: | 1444 ) |
1126 if cwd != "/": | |
1127 dirname = cwd + "/" + name[:-1] | |
1128 else: | |
1129 dirname = "/" + name[:-1] | |
1130 else: | |
1131 dirname = name[:-1] | |
1132 dlg = DeleteFilesConfirmationDialog( | 1445 dlg = DeleteFilesConfirmationDialog( |
1133 self, | 1446 self, |
1134 self.tr("Delete Directory"), | 1447 self.tr("Delete Directory"), |
1135 self.tr("Do you really want to delete this directory?"), | 1448 self.tr("Do you really want to delete this directory?"), |
1136 [dirname], | 1449 [dirname], |
1146 """ | 1459 """ |
1147 if self.__repl.deviceSupportsLocalFileAccess(): | 1460 if self.__repl.deviceSupportsLocalFileAccess(): |
1148 self.__deleteLocalDirectoryTree(True) | 1461 self.__deleteLocalDirectoryTree(True) |
1149 else: | 1462 else: |
1150 if bool(len(self.deviceFileTreeWidget.selectedItems())): | 1463 if bool(len(self.deviceFileTreeWidget.selectedItems())): |
1151 name = self.deviceFileTreeWidget.selectedItems()[0].text(0) | 1464 dirname = self.deviceFileTreeWidget.selectedItems()[0].data( |
1152 cwd = self.deviceCwd.text() | 1465 0, Qt.ItemDataRole.UserRole |
1153 if cwd: | 1466 ) |
1154 if cwd != "/": | |
1155 dirname = cwd + "/" + name[:-1] | |
1156 else: | |
1157 dirname = "/" + name[:-1] | |
1158 else: | |
1159 dirname = name[:-1] | |
1160 dlg = DeleteFilesConfirmationDialog( | 1467 dlg = DeleteFilesConfirmationDialog( |
1161 self, | 1468 self, |
1162 self.tr("Delete Directory Tree"), | 1469 self.tr("Delete Directory Tree"), |
1163 self.tr("Do you really want to delete this directory tree?"), | 1470 self.tr("Do you really want to delete this directory tree?"), |
1164 [dirname], | 1471 [dirname], |
1172 Private slot to delete a file. | 1479 Private slot to delete a file. |
1173 """ | 1480 """ |
1174 if self.__repl.deviceSupportsLocalFileAccess(): | 1481 if self.__repl.deviceSupportsLocalFileAccess(): |
1175 self.__deleteLocalFile(True) | 1482 self.__deleteLocalFile(True) |
1176 else: | 1483 else: |
1177 if bool(len(self.deviceFileTreeWidget.selectedItems())): | 1484 if bool(self.deviceFileTreeWidget.selectedItems()): |
1178 name = self.deviceFileTreeWidget.selectedItems()[0].text(0) | 1485 filename = self.deviceFileTreeWidget.selectedItems()[0].data( |
1179 dirname = self.deviceCwd.text() | 1486 0, Qt.ItemDataRole.UserRole |
1180 if dirname: | 1487 ) |
1181 if dirname != "/": | |
1182 filename = dirname + "/" + name | |
1183 else: | |
1184 filename = "/" + name | |
1185 else: | |
1186 filename = name | |
1187 dlg = DeleteFilesConfirmationDialog( | 1488 dlg = DeleteFilesConfirmationDialog( |
1188 self, | 1489 self, |
1189 self.tr("Delete File"), | 1490 self.tr("Delete File"), |
1190 self.tr("Do you really want to delete this file?"), | 1491 self.tr("Do you really want to delete this file?"), |
1191 [filename], | 1492 [filename], |
1199 Private slot to rename a file on the device. | 1500 Private slot to rename a file on the device. |
1200 """ | 1501 """ |
1201 if self.__repl.deviceSupportsLocalFileAccess(): | 1502 if self.__repl.deviceSupportsLocalFileAccess(): |
1202 self.__renameLocalFile(True) | 1503 self.__renameLocalFile(True) |
1203 else: | 1504 else: |
1204 if bool(len(self.deviceFileTreeWidget.selectedItems())): | 1505 if bool(self.deviceFileTreeWidget.selectedItems()): |
1205 name = self.deviceFileTreeWidget.selectedItems()[0].text(0) | 1506 filename = self.deviceFileTreeWidget.selectedItems()[0].data( |
1206 dirname = self.deviceCwd.text() | 1507 0, Qt.ItemDataRole.UserRole |
1207 if dirname: | 1508 ) |
1208 if dirname != "/": | |
1209 filename = dirname + "/" + name | |
1210 else: | |
1211 filename = "/" + name | |
1212 else: | |
1213 filename = name | |
1214 newname, ok = QInputDialog.getText( | 1509 newname, ok = QInputDialog.getText( |
1215 self, | 1510 self, |
1216 self.tr("Rename File"), | 1511 self.tr("Rename File"), |
1217 self.tr("Enter the new path for the file"), | 1512 self.tr("Enter the new path for the file"), |
1218 QLineEdit.EchoMode.Normal, | 1513 QLineEdit.EchoMode.Normal, |
1269 else: | 1564 else: |
1270 msg += self.tr( | 1565 msg += self.tr( |
1271 "<p>No file systems or file system information available.</p>" | 1566 "<p>No file systems or file system information available.</p>" |
1272 ) | 1567 ) |
1273 EricMessageBox.information(self, self.tr("Filesystem Information"), msg) | 1568 EricMessageBox.information(self, self.tr("Filesystem Information"), msg) |
1569 | |
1570 @pyqtSlot() | |
1571 def __clearDeviceSelection(self): | |
1572 """ | |
1573 Private slot to clear the local selection. | |
1574 """ | |
1575 for item in self.deviceFileTreeWidget.selectedItems()[:]: | |
1576 item.setSelected(False) |