pyqt-code-editor 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. pyqt_code_editor/__init__.py +5 -0
  2. pyqt_code_editor/_settings.py +232 -0
  3. pyqt_code_editor/app.py +438 -0
  4. pyqt_code_editor/code_editors/__init__.py +1 -0
  5. pyqt_code_editor/code_editors/code_editors.py +33 -0
  6. pyqt_code_editor/code_editors/languages/__init__.py +0 -0
  7. pyqt_code_editor/code_editors/languages/generic.py +11 -0
  8. pyqt_code_editor/code_editors/languages/markdown.py +11 -0
  9. pyqt_code_editor/code_editors/languages/python.py +13 -0
  10. pyqt_code_editor/code_editors/languages/text.py +9 -0
  11. pyqt_code_editor/components/__init__.py +0 -0
  12. pyqt_code_editor/components/editor_panel.py +399 -0
  13. pyqt_code_editor/components/find_in_files.py +199 -0
  14. pyqt_code_editor/components/jupyter_console.py +487 -0
  15. pyqt_code_editor/components/project_explorer.py +515 -0
  16. pyqt_code_editor/components/settings_panel.py +162 -0
  17. pyqt_code_editor/components/sigmund.py +75 -0
  18. pyqt_code_editor/components/workspace_explorer.py +162 -0
  19. pyqt_code_editor/mixins/__init__.py +15 -0
  20. pyqt_code_editor/mixins/auto_pair.py +201 -0
  21. pyqt_code_editor/mixins/base.py +142 -0
  22. pyqt_code_editor/mixins/check.py +94 -0
  23. pyqt_code_editor/mixins/comment.py +91 -0
  24. pyqt_code_editor/mixins/complete.py +482 -0
  25. pyqt_code_editor/mixins/execute.py +182 -0
  26. pyqt_code_editor/mixins/file_link.py +166 -0
  27. pyqt_code_editor/mixins/line_number.py +206 -0
  28. pyqt_code_editor/mixins/python_auto_indent.py +239 -0
  29. pyqt_code_editor/mixins/search_replace.py +396 -0
  30. pyqt_code_editor/mixins/shortcuts.py +228 -0
  31. pyqt_code_editor/mixins/smart_backspace_delete.py +120 -0
  32. pyqt_code_editor/mixins/symbols.py +45 -0
  33. pyqt_code_editor/mixins/theme.py +99 -0
  34. pyqt_code_editor/mixins/widgets/__init__.py +0 -0
  35. pyqt_code_editor/mixins/zoom.py +51 -0
  36. pyqt_code_editor/python_utils.py +936 -0
  37. pyqt_code_editor/signal_router.py +116 -0
  38. pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
  39. pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
  40. pyqt_code_editor/syntax_highlighters/languages/generic.py +86 -0
  41. pyqt_code_editor/syntax_highlighters/languages/python.py +62 -0
  42. pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +26 -0
  43. pyqt_code_editor/themes.py +36 -0
  44. pyqt_code_editor/utils.py +96 -0
  45. pyqt_code_editor/watchdog.py +141 -0
  46. pyqt_code_editor/widgets/__init__.py +6 -0
  47. pyqt_code_editor/widgets/calltip_widget.py +44 -0
  48. pyqt_code_editor/widgets/completion_popup.py +89 -0
  49. pyqt_code_editor/widgets/dock.py +15 -0
  50. pyqt_code_editor/widgets/quick_open_dialog.py +196 -0
  51. pyqt_code_editor/widgets/quick_open_file_dialog.py +32 -0
  52. pyqt_code_editor/widgets/quick_symbol_dialog.py +13 -0
  53. pyqt_code_editor/widgets/search_replace_frame.py +128 -0
  54. pyqt_code_editor/widgets/tab_splitter.py +22 -0
  55. pyqt_code_editor/widgets/tabbed_editor.py +351 -0
  56. pyqt_code_editor/worker/__init__.py +0 -0
  57. pyqt_code_editor/worker/languages/__init__.py +0 -0
  58. pyqt_code_editor/worker/languages/generic.py +15 -0
  59. pyqt_code_editor/worker/languages/python.py +17 -0
  60. pyqt_code_editor/worker/manager.py +113 -0
  61. pyqt_code_editor/worker/process.py +128 -0
  62. pyqt_code_editor/worker/providers/__init__.py +1 -0
  63. pyqt_code_editor/worker/providers/codestral.py +56 -0
  64. pyqt_code_editor/worker/providers/jedi.py +115 -0
  65. pyqt_code_editor/worker/providers/ruff.py +45 -0
  66. pyqt_code_editor/worker/providers/sigmund.py +29 -0
  67. pyqt_code_editor/worker/providers/symbol.py +78 -0
  68. pyqt_code_editor/worker/settings.py +1 -0
  69. pyqt_code_editor-0.0.1.dist-info/METADATA +69 -0
  70. pyqt_code_editor-0.0.1.dist-info/RECORD +73 -0
  71. pyqt_code_editor-0.0.1.dist-info/WHEEL +4 -0
  72. pyqt_code_editor-0.0.1.dist-info/entry_points.txt +4 -0
  73. pyqt_code_editor-0.0.1.dist-info/licenses/COPYING +674 -0
@@ -0,0 +1,89 @@
1
+ from qtpy.QtCore import Qt
2
+ from qtpy.QtGui import QTextCursor
3
+ from qtpy.QtWidgets import QListWidget, QListWidgetItem
4
+ from .. import settings
5
+
6
+
7
+ class CompletionPopup(QListWidget):
8
+ """
9
+ A popup widget to display multiple completion suggestions,
10
+ but it does NOT automatically grab focus or hide on focusOut
11
+ so that it won't flicker when the user keeps typing in the editor.
12
+ """
13
+ def __init__(self, editor):
14
+ super().__init__(editor)
15
+ self.editor = editor
16
+ self._completion_map = {}
17
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
18
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
19
+ if editor.code_editor_colors is not None:
20
+ self.setStyleSheet(f'''QListWidget {{
21
+ color: {editor.code_editor_colors['text']};
22
+ background-color: {editor.code_editor_colors['background']};
23
+ font: {settings.font_size}pt '{settings.font_family}';
24
+ border: 1px solid {editor.code_editor_colors['border']};
25
+ }}''')
26
+ # Use a frameless tool window so it can float above the editor
27
+ # without stealing focus. Note we do NOT use Qt.Popup here.
28
+ self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool)
29
+
30
+ # Let the editor keep focus
31
+ self.setFocusPolicy(Qt.NoFocus)
32
+
33
+ # Single selection mode
34
+ self.setSelectionMode(self.SingleSelection)
35
+
36
+ # Insert the chosen completion when activated
37
+ self.itemActivated.connect(self.insert_completion)
38
+
39
+ def show_completions(self, completions):
40
+ """
41
+ Show/update the list of completions near the editor's cursor,
42
+ without grabbing focus or hiding automatically.
43
+ """
44
+ self.clear()
45
+ for c in completions:
46
+ self._completion_map[c['name']] = c['name']
47
+ QListWidgetItem(c['name'], self)
48
+
49
+ if not completions:
50
+ self.hide()
51
+ return
52
+
53
+ # Place near the text cursor, taking into account that the viewport
54
+ # might change due to line numbers or other things to the left of the
55
+ # editor
56
+ cursor_rect = self.editor.cursorRect()
57
+ cursor_rect.moveRight(cursor_rect.left() + self.editor.viewportMargins().left())
58
+ global_pos = self.editor.mapToGlobal(cursor_rect.bottomLeft())
59
+ self.move(global_pos)
60
+
61
+ # Resize to fit the number of completions (within reason)
62
+ self.setCurrentRow(0)
63
+ self.resize(min(500, self.sizeHintForColumn(0) + 8),
64
+ min(500, self.sizeHintForRow(0) * len(completions) + 8))
65
+
66
+ # If we are already visible, no need to hide/show
67
+ # Just continue to show it in the new position.
68
+ if not self.isVisible():
69
+ self.show()
70
+
71
+ def focusOutEvent(self, event):
72
+ """
73
+ We override focusOutEvent but don't hide the popup
74
+ so we can continue showing suggestions while the editor has focus.
75
+ """
76
+ # If you want it to hide whenever it loses focus, uncomment:
77
+ super().focusOutEvent(event)
78
+
79
+ def insert_completion(self, item):
80
+ """
81
+ Inserts the selected completion text into the editor at the current cursor position.
82
+ """
83
+ if not item:
84
+ return
85
+ name = item.text()
86
+ completion = self._completion_map.get(name)
87
+ if not completion:
88
+ return
89
+ self.editor._cm_insert_completion(completion)
@@ -0,0 +1,15 @@
1
+ from qtpy.QtCore import Signal
2
+ from qtpy.QtWidgets import QDockWidget
3
+
4
+
5
+ class Dock(QDockWidget):
6
+
7
+ close_requested = Signal()
8
+
9
+ def __init__(self, *args, **kwargs):
10
+ super().__init__(*args, **kwargs)
11
+
12
+ def closeEvent(self, event):
13
+ event.ignore()
14
+ self.hide()
15
+ self.close_requested.emit()
@@ -0,0 +1,196 @@
1
+ import os
2
+ import logging
3
+ from qtpy.QtWidgets import (
4
+ QDialog,
5
+ QVBoxLayout,
6
+ QLineEdit,
7
+ QListView,
8
+ QAbstractItemView,
9
+ QApplication
10
+ )
11
+ from qtpy.QtCore import Qt, QSortFilterProxyModel, QModelIndex
12
+ from qtpy.QtGui import QStandardItemModel, QStandardItem
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class MultiNeedleFilterProxyModel(QSortFilterProxyModel):
17
+ """
18
+ Custom proxy model that splits the user input string into
19
+ multiple tokens (space-delimited). Each token must appear
20
+ somewhere in the item text (case-insensitive) for it to match.
21
+ """
22
+ def __init__(self, parent=None):
23
+ super().__init__(parent)
24
+ self.setFilterCaseSensitivity(Qt.CaseInsensitive)
25
+ self._needles = []
26
+
27
+ def setFilterString(self, text):
28
+ # Split on whitespace to get multiple tokens
29
+ self._needles = text.strip().split()
30
+ self.invalidateFilter()
31
+
32
+ def filterAcceptsRow(self, source_row, source_parent):
33
+ if not self._needles:
34
+ return True # If no input, show all
35
+ index = self.sourceModel().index(source_row, 0, source_parent)
36
+ item_text = index.data(Qt.DisplayRole) or ""
37
+ item_text_lower = item_text.lower()
38
+
39
+ # Each needle must be present in the text
40
+ for needle in self._needles:
41
+ needle = needle.lower()
42
+ if needle not in item_text_lower:
43
+ return False
44
+ return True
45
+
46
+ class QuickOpenDialog(QDialog):
47
+ """
48
+ A generic quick-open dialog for filtering and selecting from a list of items, each a dict
49
+ with at least a 'name' key.
50
+
51
+ Subclasses should override on_item_selected(item_dict) to handle the chosen item.
52
+ """
53
+ def __init__(self, parent, items, title="Quick Open"):
54
+ super().__init__(parent)
55
+ self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
56
+ self.setWindowModality(Qt.ApplicationModal)
57
+
58
+ self.setWindowTitle(title)
59
+ self.resize(400, 400)
60
+
61
+ layout = QVBoxLayout(self)
62
+ self._filter_edit = QLineEdit(self)
63
+ self._filter_edit.setPlaceholderText("Type to filter...")
64
+ layout.addWidget(self._filter_edit)
65
+
66
+ self._list_view = QListView(self)
67
+ self._list_view.setSelectionMode(QAbstractItemView.SingleSelection)
68
+ layout.addWidget(self._list_view)
69
+
70
+ self._items = items
71
+ self._item_model = QStandardItemModel(self)
72
+ self._proxy_model = MultiNeedleFilterProxyModel(self)
73
+ self._proxy_model.setSourceModel(self._item_model)
74
+ self._list_view.setModel(self._proxy_model)
75
+
76
+ # Populate the model with items
77
+ self._populate_model()
78
+
79
+ # Ensure we select the top item whenever the filter results change
80
+ self._proxy_model.layoutChanged.connect(self._select_top_item_if_available)
81
+
82
+ # Connect signals
83
+ self._filter_edit.textChanged.connect(self._proxy_model.setFilterString)
84
+ self._list_view.doubleClicked.connect(self._on_item_double_clicked)
85
+
86
+ # Attempt to select top item immediately
87
+ self._select_top_item_if_available()
88
+
89
+ def _populate_model(self):
90
+ for item_dict in self._items:
91
+ name = item_dict.get("name", "")
92
+ item = QStandardItem(name)
93
+ # Store the entire dict in UserRole
94
+ item.setData(item_dict, Qt.UserRole)
95
+ self._item_model.appendRow(item)
96
+
97
+ def _on_item_double_clicked(self, proxy_index: QModelIndex):
98
+ source_index = self._proxy_model.mapToSource(proxy_index)
99
+ item = self._item_model.itemFromIndex(source_index)
100
+ item_dict = item.data(Qt.UserRole)
101
+ self.on_item_selected(item_dict)
102
+ self.accept()
103
+
104
+ def _select_top_item_if_available(self):
105
+ """Select the top item if it exists, otherwise clear selection."""
106
+ count = self._proxy_model.rowCount()
107
+ if count > 0:
108
+ top_index = self._proxy_model.index(0, 0)
109
+ self._list_view.setCurrentIndex(top_index)
110
+ else:
111
+ self._list_view.clearSelection()
112
+
113
+ def on_item_selected(self, item_dict: dict):
114
+ """
115
+ Override this in subclasses to define what happens when an item is chosen.
116
+ """
117
+ raise NotImplementedError("Subclasses must implement on_item_selected()")
118
+
119
+ def keyPressEvent(self, event):
120
+ """Handle Enter for selection and Up/Down wrap-around navigation."""
121
+ key = event.key()
122
+ count = self._proxy_model.rowCount()
123
+
124
+ # If there are no matches, ignore arrow and page navigation
125
+ if count == 0:
126
+ if key in (
127
+ Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown,
128
+ Qt.Key_Home, Qt.Key_End
129
+ ):
130
+ return
131
+
132
+ if key in (Qt.Key_Return, Qt.Key_Enter):
133
+ current_index = self._list_view.currentIndex()
134
+ if current_index.isValid():
135
+ proxy_index = current_index
136
+ else:
137
+ # If nothing is selected, fall back to top item if available
138
+ proxy_index = self._proxy_model.index(0, 0) if count > 0 else None
139
+
140
+ if proxy_index is not None and proxy_index.isValid():
141
+ source_index = self._proxy_model.mapToSource(proxy_index)
142
+ item = self._item_model.itemFromIndex(source_index)
143
+ item_dict = item.data(Qt.UserRole)
144
+ self.on_item_selected(item_dict)
145
+ self.accept()
146
+
147
+ elif key == Qt.Key_Up:
148
+ if count > 0:
149
+ current_index = self._list_view.currentIndex()
150
+ if current_index.isValid():
151
+ row = current_index.row()
152
+ if row <= 0:
153
+ # wrap to last
154
+ self._list_view.setCurrentIndex(
155
+ self._proxy_model.index(count - 1, 0)
156
+ )
157
+ else:
158
+ self._list_view.setCurrentIndex(
159
+ self._proxy_model.index(row - 1, 0)
160
+ )
161
+ else:
162
+ # If nothing is selected, go to last
163
+ self._list_view.setCurrentIndex(
164
+ self._proxy_model.index(count - 1, 0)
165
+ )
166
+ return
167
+
168
+ elif key == Qt.Key_Down:
169
+ if count > 0:
170
+ current_index = self._list_view.currentIndex()
171
+ if current_index.isValid():
172
+ row = current_index.row()
173
+ if row >= count - 1:
174
+ # wrap to first
175
+ self._list_view.setCurrentIndex(
176
+ self._proxy_model.index(0, 0)
177
+ )
178
+ else:
179
+ self._list_view.setCurrentIndex(
180
+ self._proxy_model.index(row + 1, 0)
181
+ )
182
+ else:
183
+ # If nothing is selected, go to first
184
+ self._list_view.setCurrentIndex(
185
+ self._proxy_model.index(0, 0)
186
+ )
187
+ return
188
+
189
+ elif key in (Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Home, Qt.Key_End):
190
+ # Let default navigation logic handle these keys
191
+ self._list_view.setFocus()
192
+ QApplication.sendEvent(self._list_view, event)
193
+ return
194
+
195
+ else:
196
+ super().keyPressEvent(event)
@@ -0,0 +1,32 @@
1
+ import os
2
+ import logging
3
+ from . import QuickOpenDialog
4
+ logger = logging.getLogger(__name__)
5
+
6
+
7
+ class QuickOpenFileDialog(QuickOpenDialog):
8
+ """
9
+ Specialized dialog that handles quickly opening files.
10
+ Collects files in a given root_path, strips common prefix,
11
+ and calls open_file_callback on selection.
12
+ """
13
+ def __init__(self, parent, file_list, open_file_callback):
14
+ self.open_file_callback = open_file_callback
15
+ items = []
16
+ if file_list:
17
+ common_prefix = os.path.commonpath(file_list)
18
+ for full_path in file_list:
19
+ relative_path = os.path.relpath(full_path, common_prefix)
20
+ items.append({
21
+ "name": relative_path,
22
+ "full_path": full_path,
23
+ })
24
+ else:
25
+ logger.warning('no files to show')
26
+ super().__init__(parent, items, title="Quick Open File")
27
+
28
+ def on_item_selected(self, item_dict: dict):
29
+ """Opens the file at item_dict['full_path'] and closes the dialog."""
30
+ full_path = item_dict.get("full_path", None)
31
+ if full_path:
32
+ self.open_file_callback(full_path)
@@ -0,0 +1,13 @@
1
+ import os
2
+ import logging
3
+ from . import QuickOpenDialog
4
+ logger = logging.getLogger(__name__)
5
+
6
+
7
+ class QuickSymbolDialog(QuickOpenDialog):
8
+ def __init__(self, parent, items, jump_to_symbol_callback):
9
+ self.jump_to_symbol_callback = jump_to_symbol_callback
10
+ super().__init__(parent, items, title="Symbols")
11
+
12
+ def on_item_selected(self, item_dict: dict):
13
+ self.jump_to_symbol_callback(item_dict)
@@ -0,0 +1,128 @@
1
+ from qtpy.QtWidgets import (
2
+ QFrame, QVBoxLayout, QHBoxLayout,
3
+ QLineEdit, QPushButton, QCheckBox, QLabel
4
+ )
5
+ from qtpy.QtCore import Qt, Signal
6
+ from .. import settings
7
+
8
+
9
+ class SearchReplaceFrame(QFrame):
10
+ """
11
+ A small widget containing 'Find' and optionally 'Replace' fields,
12
+ plus checkboxes and buttons for next/prev/replace/replace all, etc.
13
+ Hides or shows replace-related UI depending on mode.
14
+ """
15
+ findNextRequested = Signal()
16
+ findPrevRequested = Signal()
17
+ replaceOneRequested = Signal()
18
+ replaceAllRequested = Signal()
19
+
20
+ def __init__(self, parent=None):
21
+ super().__init__(parent)
22
+
23
+ self.setFrameShape(QFrame.StyledPanel)
24
+ self.setFrameShadow(QFrame.Raised)
25
+ editor = parent
26
+ if editor.code_editor_colors is not None:
27
+ self.setStyleSheet(f'''
28
+ QCheckBox,
29
+ QPushButton,
30
+ QLineEdit,
31
+ QFrame {{
32
+ color: {editor.code_editor_colors['text']};
33
+ background-color: {editor.code_editor_colors['background']};
34
+ font: {settings.font_size}pt '{settings.font_family}';
35
+ padding: 8px;
36
+ }}
37
+ QCheckBox::indicator,
38
+ QPushButton,
39
+ QLineEdit,
40
+ QFrame {{
41
+ border-color: {editor.code_editor_colors['border']};
42
+ border-width: 1px;
43
+ border-style: solid;
44
+ border-radius: 4px;
45
+ }}
46
+ QCheckBox::indicator:checked {{
47
+ background-color: {editor.code_editor_colors['text']};
48
+ }}
49
+ QLabel {{
50
+ border: none;
51
+ }}
52
+ ''')
53
+ self.setWindowFlags(Qt.SubWindow | Qt.FramelessWindowHint)
54
+
55
+ # Layout
56
+ layout = QVBoxLayout(self)
57
+ layout.setContentsMargins(5, 5, 5, 5)
58
+
59
+ # Find row
60
+ findRow = QHBoxLayout()
61
+
62
+ self.findLabel = QLabel("Find:", self)
63
+ self.matchCountLabel = QLabel("0 of 0", self)
64
+ self.findEdit = QLineEdit(self)
65
+ self.caseBox = QCheckBox("Aa", self)
66
+ self.caseBox.setToolTip("Case Sensitive")
67
+ self.regexBox = QCheckBox(".*", self)
68
+ self.regexBox.setToolTip("Use Regular Expressions")
69
+ self.wholeWordBox = QCheckBox("\\b", self)
70
+ self.wholeWordBox.setToolTip("Match Whole Word")
71
+
72
+ self.findNextBtn = QPushButton("Next", self)
73
+ self.findPrevBtn = QPushButton("Prev", self)
74
+
75
+ findRow.addWidget(self.findLabel)
76
+ findRow.addWidget(self.findEdit)
77
+ findRow.addWidget(self.matchCountLabel)
78
+ findRow.addWidget(self.caseBox)
79
+ findRow.addWidget(self.regexBox)
80
+ findRow.addWidget(self.wholeWordBox)
81
+ findRow.addWidget(self.findNextBtn)
82
+ findRow.addWidget(self.findPrevBtn)
83
+
84
+ layout.addLayout(findRow)
85
+
86
+ # Replace row
87
+ replaceRow = QHBoxLayout()
88
+ self.replaceLabel = QLabel("Replace:", self)
89
+ self.replaceEdit = QLineEdit(self)
90
+ self.replaceBtn = QPushButton("Replace", self)
91
+ self.replaceAllBtn = QPushButton("Replace All", self)
92
+
93
+ replaceRow.addWidget(self.replaceLabel)
94
+ replaceRow.addWidget(self.replaceEdit)
95
+ replaceRow.addWidget(self.replaceBtn)
96
+ replaceRow.addWidget(self.replaceAllBtn)
97
+
98
+ layout.addLayout(replaceRow)
99
+
100
+ self.replaceRowWidget = replaceRow # Keep reference to manage visibility
101
+
102
+ # Connections
103
+ self.findNextBtn.clicked.connect(self.findNextRequested)
104
+ self.findPrevBtn.clicked.connect(self.findPrevRequested)
105
+ self.replaceBtn.clicked.connect(self.replaceOneRequested)
106
+ self.replaceAllBtn.clicked.connect(self.replaceAllRequested)
107
+
108
+ # Default: searching only, so hide the "replace" row
109
+ self.showSearchOnly()
110
+
111
+ def showSearchOnly(self):
112
+ # Hide replace UI
113
+ self.replaceLabel.setVisible(False)
114
+ self.replaceEdit.setVisible(False)
115
+ self.replaceBtn.setVisible(False)
116
+ self.replaceAllBtn.setVisible(False)
117
+ self.adjustSize()
118
+ self.resize(self.sizeHint())
119
+
120
+ def showSearchReplace(self):
121
+ # Show replace UI
122
+ self.replaceLabel.setVisible(True)
123
+ self.replaceEdit.setVisible(True)
124
+ self.replaceBtn.setVisible(True)
125
+ self.replaceAllBtn.setVisible(True)
126
+ self.adjustSize()
127
+ self.resize(self.sizeHint())
128
+
@@ -0,0 +1,22 @@
1
+ from qtpy.QtWidgets import QSplitter
2
+
3
+
4
+ class TabSplitter(QSplitter):
5
+
6
+ def editors(self):
7
+ """Returns a list of all in this splitter and all sub splitters"""
8
+ editors = []
9
+ for i in range(self.count()):
10
+ editors += self.widget(i).editors()
11
+ return editors
12
+
13
+ def tab_widgets(self):
14
+ """Returns a list of all tab widgets in this splitter and all sub splitters"""
15
+ tab_widgets = []
16
+ for i in range(self.count()):
17
+ widget = self.widget(i)
18
+ if isinstance(widget, TabSplitter):
19
+ tab_widgets += widget.tab_widgets()
20
+ else:
21
+ tab_widgets.append(widget)
22
+ return tab_widgets