src/eric7/EricWidgets/EricPathPicker.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9152
8a68afaf1ba2
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2015 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a path picker widget.
8 """
9
10 import enum
11 import os
12 import pathlib
13
14 from PyQt6.QtCore import pyqtSignal, Qt, QCoreApplication, QDir
15 from PyQt6.QtWidgets import (
16 QWidget, QHBoxLayout, QToolButton, QSizePolicy, QLineEdit, QComboBox
17 )
18
19 from . import EricFileDialog
20 from .EricCompleters import EricFileCompleter, EricDirCompleter
21
22 import UI.PixmapCache
23
24
25 class EricPathPickerModes(enum.Enum):
26 """
27 Class implementing the path picker modes.
28 """
29 OPEN_FILE_MODE = 0
30 OPEN_FILES_MODE = 1
31 SAVE_FILE_MODE = 2
32 SAVE_FILE_ENSURE_EXTENSION_MODE = 3
33 SAVE_FILE_OVERWRITE_MODE = 4
34 DIRECTORY_MODE = 5
35 DIRECTORY_SHOW_FILES_MODE = 6
36 OPEN_FILES_AND_DIRS_MODE = 7
37 CUSTOM_MODE = 99
38 NO_MODE = 100
39
40
41 class EricPathPickerBase(QWidget):
42 """
43 Class implementing the base of a path picker widget consisting of a
44 line edit or combo box and a tool button to open a file dialog.
45
46 @signal textChanged(path) emitted when the entered path has changed
47 (line edit based widget)
48 @signal editTextChanged(path) emitted when the entered path has changed
49 (combo box based widget)
50 @signal pathSelected(path) emitted after a path has been selected via the
51 file dialog
52 @signal aboutToShowPathPickerDialog emitted before the file dialog is shown
53 @signal pickerButtonClicked emitted when the picker button was pressed and
54 the widget mode is custom
55 """
56 DefaultMode = EricPathPickerModes.NO_MODE
57
58 textChanged = pyqtSignal(str)
59 editTextChanged = pyqtSignal(str)
60 pathSelected = pyqtSignal(str)
61 aboutToShowPathPickerDialog = pyqtSignal()
62 pickerButtonClicked = pyqtSignal()
63
64 def __init__(self, parent=None, useLineEdit=True):
65 """
66 Constructor
67
68 @param parent reference to the parent widget
69 @type QWidget
70 @param useLineEdit flag indicating the use of a line edit
71 @type bool
72 """
73 super().__init__(parent)
74
75 self.__lineEditKind = useLineEdit
76
77 self.__mode = EricPathPicker.DefaultMode
78 self.__editorEnabled = True
79
80 self._completer = None
81 self.__filters = ""
82 self.__defaultDirectory = ""
83 self.__windowTitle = ""
84
85 self.__layout = QHBoxLayout(self)
86 self.__layout.setSpacing(0)
87 self.__layout.setContentsMargins(0, 0, 0, 0)
88 self.setLayout(self.__layout)
89
90 if useLineEdit:
91 self._editor = QLineEdit(self)
92 self._editor.setPlaceholderText(QCoreApplication.translate(
93 "EricPathPickerBase", "Enter Path Name"))
94 self._editor.setClearButtonEnabled(True)
95 else:
96 self._editor = QComboBox(self)
97 self._editor.setEditable(True)
98 self._editor.lineEdit().setPlaceholderText(
99 QCoreApplication.translate(
100 "EricPathPickerBase", "Enter Path Name"))
101 self._editor.lineEdit().setClearButtonEnabled(True)
102
103 self.__button = QToolButton(self)
104 self.__button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
105 self.__button.setIcon(UI.PixmapCache.getIcon("open"))
106
107 self.__layout.addWidget(self._editor)
108 self.__layout.addWidget(self.__button)
109
110 self.__button.clicked.connect(self.__showPathPickerDialog)
111 if useLineEdit:
112 self._editor.textEdited.connect(self.__pathEdited)
113 self._editor.textChanged.connect(self.textChanged)
114 else:
115 self._editor.editTextChanged.connect(self.editTextChanged)
116
117 self.setFocusProxy(self._editor)
118 self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
119 self.setSizePolicy(QSizePolicy.Policy.Expanding,
120 QSizePolicy.Policy.Preferred)
121
122 self.__button.setEnabled(self.__mode != EricPathPickerModes.NO_MODE)
123
124 def __pathEdited(self, fpath):
125 """
126 Private slot handling editing of the path.
127
128 @param fpath current text of the path line edit
129 @type str
130 """
131 if self._completer and not self._completer.popup().isVisible():
132 self._completer.setRootPath(QDir.toNativeSeparators(fpath))
133
134 def setMode(self, mode):
135 """
136 Public method to set the path picker mode.
137
138 @param mode picker mode
139 @type EricPathPickerModes
140 @exception ValueError raised to indicate a bad parameter value
141 """
142 if mode not in EricPathPickerModes:
143 raise ValueError("Bad value for 'mode' parameter.")
144
145 oldMode = self.__mode
146 self.__mode = mode
147
148 if mode != oldMode or (self.__lineEditKind and not self._completer):
149 if self.__lineEditKind and self._completer:
150 # Remove current completer
151 self._editor.setCompleter(None)
152 self._completer = None
153
154 if mode != EricPathPickerModes.NO_MODE:
155 if self.__lineEditKind:
156 # Set a new completer
157 if mode == EricPathPickerModes.DIRECTORY_MODE:
158 self._completer = EricDirCompleter(self._editor)
159 else:
160 self._completer = EricFileCompleter(self._editor)
161
162 # set inactive text
163 if mode in (
164 EricPathPickerModes.OPEN_FILES_MODE,
165 EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
166 ):
167 self._editor.setPlaceholderText(
168 self.tr("Enter Path Names separated by ';'"))
169 else:
170 self._editor.setPlaceholderText(
171 self.tr("Enter Path Name"))
172 self.__button.setEnabled(self.__mode != EricPathPickerModes.NO_MODE)
173
174 def mode(self):
175 """
176 Public method to get the path picker mode.
177
178 @return path picker mode
179 @rtype EricPathPickerModes
180 """
181 return self.__mode
182
183 def setPickerEnabled(self, enable):
184 """
185 Public method to set the enabled state of the file dialog button.
186
187 @param enable flag indicating the enabled state
188 @type bool
189 """
190 self.__button.setEnabled(enable)
191
192 def isPickerEnabled(self):
193 """
194 Public method to get the file dialog button enabled state.
195
196 @return flag indicating the enabled state
197 @rtype bool
198 """
199 return self.__button.isEnabled()
200
201 def clear(self):
202 """
203 Public method to clear the current path or list of paths.
204 """
205 self._editor.clear()
206
207 def clearEditText(self):
208 """
209 Public method to clear the current path.
210 """
211 if not self.__lineEditKind:
212 self._editor.clearEditText()
213
214 def _setEditorText(self, text):
215 """
216 Protected method to set the text of the editor.
217
218 @param text text to set
219 @type str
220 """
221 if self.__lineEditKind:
222 self._editor.setText(text)
223 else:
224 self._editor.setEditText(text)
225 if text and self._editor.findText(text) == -1:
226 self._editor.insertItem(0, text)
227
228 def _editorText(self):
229 """
230 Protected method to get the text of the editor.
231
232 @return text of the editor
233 @rtype str
234 """
235 if self.__lineEditKind:
236 return self._editor.text()
237 else:
238 return self._editor.currentText()
239
240 def setText(self, fpath, toNative=True):
241 """
242 Public method to set the current path.
243
244 @param fpath path to be set
245 @type str
246 @param toNative flag indicating to convert the path into
247 a native format
248 @type bool
249 """
250 if self.__mode in (
251 EricPathPickerModes.OPEN_FILES_MODE,
252 EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
253 ):
254 self._setEditorText(fpath)
255 else:
256 if toNative:
257 fpath = QDir.toNativeSeparators(fpath)
258 self._setEditorText(fpath)
259 if self._completer:
260 self._completer.setRootPath(fpath)
261
262 def text(self, toNative=True):
263 """
264 Public method to get the current path.
265
266 @param toNative flag indicating to convert the path into
267 a native format
268 @type bool
269 @return current path
270 @rtype str
271 """
272 if self.__mode in (
273 EricPathPickerModes.OPEN_FILES_MODE,
274 EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
275 ):
276 if toNative:
277 return ";".join(
278 [QDir.toNativeSeparators(fpath)
279 for fpath in self._editorText().split(";")])
280 else:
281 return self._editorText()
282 else:
283 if toNative:
284 return os.path.expanduser(
285 QDir.toNativeSeparators(self._editorText()))
286 else:
287 return os.path.expanduser(self._editorText())
288
289 def setEditText(self, fpath, toNative=True):
290 """
291 Public method to set the current path.
292
293 @param fpath path to be set
294 @type str
295 @param toNative flag indicating to convert the path into
296 a native format
297 @type bool
298 """
299 self.setText(fpath, toNative=toNative)
300
301 def currentText(self, toNative=True):
302 """
303 Public method to get the current path.
304
305 @param toNative flag indicating to convert the path into
306 a native format
307 @type bool
308 @return current path
309 @rtype str
310 """
311 return self.text(toNative=toNative)
312
313 def setPath(self, fpath, toNative=True):
314 """
315 Public method to set the current path.
316
317 @param fpath path to be set
318 @type str
319 @param toNative flag indicating to convert the path into
320 a native format
321 @type bool
322 """
323 self.setText(fpath, toNative=toNative)
324
325 def path(self, toNative=True):
326 """
327 Public method to get the current path.
328
329 @param toNative flag indicating to convert the path into
330 a native format
331 @type bool
332 @return current path
333 @rtype str
334 """
335 return self.text(toNative=toNative)
336
337 def paths(self, toNative=True):
338 """
339 Public method to get the list of entered paths.
340
341 @param toNative flag indicating to convert the path into
342 a native format
343 @type bool
344 @return entered paths
345 @rtype list of str
346 """
347 if self.__mode in (
348 EricPathPickerModes.OPEN_FILES_MODE,
349 EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
350 ):
351 return self.path(toNative=toNative).split(";")
352 else:
353 return [self.path(toNative=toNative)]
354
355 def firstPath(self, toNative=True):
356 """
357 Public method to get the first path of a list of entered paths.
358
359 @param toNative flag indicating to convert the path into
360 a native format
361 @type bool
362 @return first path
363 @rtype str
364 """
365 return self.paths(toNative=toNative)[0]
366
367 def lastPath(self, toNative=True):
368 """
369 Public method to get the last path of a list of entered paths.
370
371 @param toNative flag indicating to convert the path into
372 a native format
373 @type bool
374 @return last path
375 @rtype str
376 """
377 return self.paths(toNative=toNative)[-1]
378
379 def setEditorEnabled(self, enable):
380 """
381 Public method to set the path editor's enabled state.
382
383 @param enable flag indicating the enable state
384 @type bool
385 """
386 if enable != self._editorEnabled:
387 self._editorEnabled = enable
388 self._editor.setEnabled(enable)
389
390 def editorEnabled(self):
391 """
392 Public method to get the path editor's enabled state.
393
394 @return flag indicating the enabled state
395 @rtype bool
396 """
397 return self._editorEnabled
398
399 def setDefaultDirectory(self, directory):
400 """
401 Public method to set the default directory.
402
403 @param directory default directory
404 @type str
405 """
406 self.__defaultDirectory = directory
407
408 def defaultDirectory(self):
409 """
410 Public method to get the default directory.
411
412 @return default directory
413 @rtype str
414 """
415 return self.__defaultDirectory
416
417 def setWindowTitle(self, title):
418 """
419 Public method to set the path picker dialog window title.
420
421 @param title window title
422 @type str
423 """
424 self.__windowTitle = title
425
426 def windowTitle(self):
427 """
428 Public method to get the path picker dialog's window title.
429
430 @return window title
431 @rtype str
432 """
433 return self.__windowTitle
434
435 def setFilters(self, filters):
436 """
437 Public method to set the filters for the path picker dialog.
438
439 Note: Multiple filters must be separated by ';;'.
440
441 @param filters string containing the file filters
442 @type str
443 """
444 self.__filters = filters
445
446 def filters(self):
447 """
448 Public methods to get the filter string.
449
450 @return filter string
451 @rtype str
452 """
453 return self.__filters
454
455 def setNameFilters(self, filters):
456 """
457 Public method to set the name filters for the completer.
458
459 @param filters list of file name filters
460 @type list of str
461 """
462 if self._completer:
463 self._completer.model().setNameFilters(filters)
464
465 def setButtonToolTip(self, tooltip):
466 """
467 Public method to set the tool button tool tip.
468
469 @param tooltip text to be set as a tool tip
470 @type str
471 """
472 self.__button.setToolTip(tooltip)
473
474 def buttonToolTip(self):
475 """
476 Public method to get the tool button tool tip.
477
478 @return tool tip text
479 @rtype str
480 """
481 return self.__button.toolTip()
482
483 def setEditorToolTip(self, tooltip):
484 """
485 Public method to set the editor tool tip.
486
487 @param tooltip text to be set as a tool tip
488 @type str
489 """
490 self._editor.setToolTip(tooltip)
491
492 def editorToolTip(self):
493 """
494 Public method to get the editor tool tip.
495
496 @return tool tip text
497 @rtype str
498 """
499 return self._editor.toolTip()
500
501 def __showPathPickerDialog(self):
502 """
503 Private slot to show the path picker dialog.
504 """
505 if self.__mode == EricPathPickerModes.NO_MODE:
506 return
507
508 if self.__mode == EricPathPickerModes.CUSTOM_MODE:
509 self.pickerButtonClicked.emit()
510 return
511
512 self.aboutToShowPathPickerDialog.emit()
513
514 windowTitle = self.__windowTitle
515 if not windowTitle:
516 if self.__mode == EricPathPickerModes.OPEN_FILE_MODE:
517 windowTitle = self.tr("Choose a file to open")
518 elif self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
519 windowTitle = self.tr("Choose files to open")
520 elif self.__mode == EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE:
521 windowTitle = self.tr("Choose files and directories")
522 elif self.__mode in [
523 EricPathPickerModes.SAVE_FILE_MODE,
524 EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE,
525 EricPathPickerModes.SAVE_FILE_OVERWRITE_MODE]:
526 windowTitle = self.tr("Choose a file to save")
527 elif self.__mode == EricPathPickerModes.DIRECTORY_MODE:
528 windowTitle = self.tr("Choose a directory")
529
530 directory = self._editorText()
531 if not directory and self.__defaultDirectory:
532 directory = self.__defaultDirectory
533 directory = (
534 os.path.expanduser(directory.split(";")[0])
535 if self.__mode in (
536 EricPathPickerModes.OPEN_FILES_MODE,
537 EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE) else
538 os.path.expanduser(directory)
539 )
540 if not os.path.isabs(directory) and self.__defaultDirectory:
541 directory = os.path.join(self.__defaultDirectory, directory)
542 directory = QDir.fromNativeSeparators(directory)
543
544 if self.__mode == EricPathPickerModes.OPEN_FILE_MODE:
545 fpath = EricFileDialog.getOpenFileName(
546 self,
547 windowTitle,
548 directory,
549 self.__filters)
550 fpath = QDir.toNativeSeparators(fpath)
551 elif self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
552 fpaths = EricFileDialog.getOpenFileNames(
553 self,
554 windowTitle,
555 directory,
556 self.__filters)
557 fpath = ";".join([QDir.toNativeSeparators(fpath)
558 for fpath in fpaths])
559 elif self.__mode == EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE:
560 fpaths = EricFileDialog.getOpenFileAndDirNames(
561 self,
562 windowTitle,
563 directory,
564 self.__filters)
565 fpath = ";".join([QDir.toNativeSeparators(fpath)
566 for fpath in fpaths])
567 elif self.__mode == EricPathPickerModes.SAVE_FILE_MODE:
568 fpath = EricFileDialog.getSaveFileName(
569 self,
570 windowTitle,
571 directory,
572 self.__filters,
573 EricFileDialog.DontConfirmOverwrite)
574 fpath = QDir.toNativeSeparators(fpath)
575 elif (
576 self.__mode == EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE
577 ):
578 fpath, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
579 self,
580 windowTitle,
581 directory,
582 self.__filters,
583 None,
584 EricFileDialog.DontConfirmOverwrite)
585 fpath = pathlib.Path(fpath)
586 if not fpath.suffix:
587 ex = selectedFilter.split("(*")[1].split(")")[0]
588 if ex:
589 fpath = fpath.with_suffix(ex)
590 elif self.__mode == EricPathPickerModes.SAVE_FILE_OVERWRITE_MODE:
591 fpath = EricFileDialog.getSaveFileName(
592 self,
593 windowTitle,
594 directory,
595 self.__filters)
596 fpath = QDir.toNativeSeparators(fpath)
597 elif self.__mode == EricPathPickerModes.DIRECTORY_MODE:
598 fpath = EricFileDialog.getExistingDirectory(
599 self,
600 windowTitle,
601 directory,
602 EricFileDialog.ShowDirsOnly)
603 fpath = QDir.toNativeSeparators(fpath)
604 while fpath.endswith(os.sep):
605 fpath = fpath[:-1]
606 elif self.__mode == EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE:
607 fpath = EricFileDialog.getExistingDirectory(
608 self,
609 windowTitle,
610 directory,
611 EricFileDialog.DontUseNativeDialog)
612 fpath = QDir.toNativeSeparators(fpath)
613 while fpath.endswith(os.sep):
614 fpath = fpath[:-1]
615
616 if fpath:
617 self._setEditorText(str(fpath))
618 self.pathSelected.emit(str(fpath))
619
620 def setReadOnly(self, readOnly):
621 """
622 Public method to set the path picker to read only mode.
623
624 @param readOnly flag indicating read only mode
625 @type bool
626 """
627 try:
628 self._editor.setReadOnly(readOnly)
629 except AttributeError:
630 self._editor.setEditable(not readOnly)
631 self.setPickerEnabled(not readOnly)
632
633 def isReadOnly(self):
634 """
635 Public method to check the path picker for read only mode.
636
637 @return flg indicating read only mode
638 @rtype bool
639 """
640 try:
641 return self._editor.isReadOnly()
642 except AttributeError:
643 return not self._editor.isEditable()
644
645 ##################################################################
646 ## Methods below emulate some of the QComboBox API
647 ##################################################################
648
649 def addItems(self, pathsList):
650 """
651 Public method to add paths to the current list.
652
653 @param pathsList list of paths to add
654 @type list of str
655 """
656 self._editor.addItems(pathsList)
657
658 def addItem(self, fpath):
659 """
660 Public method to add a paths to the current list.
661
662 @param fpath path to add
663 @type str
664 """
665 self._editor.addItem(fpath)
666
667 def setPathsList(self, pathsList):
668 """
669 Public method to set the paths list.
670
671 @param pathsList list of paths
672 @type list of str
673 """
674 self.clear()
675 self.addItems(pathsList)
676
677 def setCurrentIndex(self, index):
678 """
679 Public slot to set the current index.
680
681 @param index index of the item to set current
682 @type int
683 """
684 self._editor.setCurrentIndex(index)
685
686 def setCurrentText(self, text):
687 """
688 Public slot to set the current text.
689
690 @param text text of the item to set current
691 @type str
692 """
693 self._editor.setCurrentText(text)
694
695 def setInsertPolicy(self, policy):
696 """
697 Public method to set the insertion policy of the combo box.
698
699 @param policy insertion policy
700 @type QComboBox.InsertPolicy
701 """
702 self._editor.setInsertPolicy(policy)
703
704 def setSizeAdjustPolicy(self, policy):
705 """
706 Public method to set the size adjust policy of the combo box.
707
708 @param policy size adjust policy
709 @type QComboBox.SizeAdjustPolicy
710 """
711 self._editor.setSizeAdjustPolicy(policy)
712
713
714 class EricPathPicker(EricPathPickerBase):
715 """
716 Class implementing a path picker widget consisting of a line edit and a
717 tool button to open a file dialog.
718 """
719 def __init__(self, parent=None):
720 """
721 Constructor
722
723 @param parent reference to the parent widget
724 @type QWidget
725 """
726 super().__init__(parent, useLineEdit=True)
727
728
729 class EricComboPathPicker(EricPathPickerBase):
730 """
731 Class implementing a path picker widget consisting of a combobox and a
732 tool button to open a file dialog.
733 """
734 def __init__(self, parent=None):
735 """
736 Constructor
737
738 @param parent reference to the parent widget
739 @type QWidget
740 """
741 super().__init__(parent, useLineEdit=False)
742
743 def getPathItems(self):
744 """
745 Public method to get the list of remembered paths.
746
747 @return list od remembered paths
748 @rtype list of str
749 """
750 paths = []
751 for index in range(self._editor.count()):
752 paths.append(self._editor.itemText(index))
753 return paths

eric ide

mercurial