PrEditor 2.1.0__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 (179) hide show
  1. preditor/__init__.py +315 -0
  2. preditor/__main__.py +13 -0
  3. preditor/about_module.py +165 -0
  4. preditor/cli.py +192 -0
  5. preditor/config.py +318 -0
  6. preditor/constants.py +13 -0
  7. preditor/contexts.py +210 -0
  8. preditor/cores/__init__.py +0 -0
  9. preditor/cores/core.py +20 -0
  10. preditor/dccs/.hab.json +10 -0
  11. preditor/dccs/maya/PrEditor_maya.mod +1 -0
  12. preditor/dccs/maya/README.md +22 -0
  13. preditor/dccs/maya/plug-ins/PrEditor_maya.py +141 -0
  14. preditor/dccs/studiomax/PackageContents.xml +32 -0
  15. preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +8 -0
  16. preditor/dccs/studiomax/README.md +17 -0
  17. preditor/dccs/studiomax/preditor.ms +16 -0
  18. preditor/dccs/studiomax/preditor_menu.mnx +7 -0
  19. preditor/debug.py +149 -0
  20. preditor/delayable_engine/__init__.py +302 -0
  21. preditor/delayable_engine/delayables.py +85 -0
  22. preditor/enum.py +728 -0
  23. preditor/excepthooks.py +165 -0
  24. preditor/gui/__init__.py +56 -0
  25. preditor/gui/app.py +163 -0
  26. preditor/gui/codehighlighter.py +289 -0
  27. preditor/gui/completer.py +237 -0
  28. preditor/gui/console.py +605 -0
  29. preditor/gui/console_base.py +911 -0
  30. preditor/gui/dialog.py +181 -0
  31. preditor/gui/drag_tab_bar.py +625 -0
  32. preditor/gui/editor_chooser.py +57 -0
  33. preditor/gui/errordialog.py +69 -0
  34. preditor/gui/find_files.py +137 -0
  35. preditor/gui/fuzzy_search/__init__.py +0 -0
  36. preditor/gui/fuzzy_search/fuzzy_search.py +97 -0
  37. preditor/gui/group_tab_widget/__init__.py +0 -0
  38. preditor/gui/group_tab_widget/group_tab_widget.py +528 -0
  39. preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
  40. preditor/gui/group_tab_widget/grouped_tab_models.py +107 -0
  41. preditor/gui/group_tab_widget/grouped_tab_widget.py +223 -0
  42. preditor/gui/group_tab_widget/one_tab_widget.py +96 -0
  43. preditor/gui/level_buttons.py +358 -0
  44. preditor/gui/logger_window_handler.py +77 -0
  45. preditor/gui/logger_window_plugin.py +35 -0
  46. preditor/gui/loggerwindow.py +2405 -0
  47. preditor/gui/newtabwidget.py +69 -0
  48. preditor/gui/output_console.py +11 -0
  49. preditor/gui/qtdesigner/__init__.py +21 -0
  50. preditor/gui/qtdesigner/_log_plugin.py +29 -0
  51. preditor/gui/qtdesigner/console_base_plugin.py +48 -0
  52. preditor/gui/qtdesigner/console_predit_plugin.py +48 -0
  53. preditor/gui/set_text_editor_path_dialog.py +61 -0
  54. preditor/gui/status_label.py +99 -0
  55. preditor/gui/suggest_path_quotes_dialog.py +50 -0
  56. preditor/gui/ui/editor_chooser.ui +93 -0
  57. preditor/gui/ui/errordialog.ui +74 -0
  58. preditor/gui/ui/find_files.ui +140 -0
  59. preditor/gui/ui/loggerwindow.ui +1909 -0
  60. preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
  61. preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
  62. preditor/gui/window.py +161 -0
  63. preditor/gui/workbox_mixin.py +1139 -0
  64. preditor/gui/workbox_text_edit.py +136 -0
  65. preditor/gui/workboxwidget.py +315 -0
  66. preditor/logging_config.py +55 -0
  67. preditor/osystem.py +401 -0
  68. preditor/plugins.py +118 -0
  69. preditor/prefs.py +381 -0
  70. preditor/resource/environment_variables.html +26 -0
  71. preditor/resource/error_mail.html +85 -0
  72. preditor/resource/error_mail_inline.html +41 -0
  73. preditor/resource/img/README.md +17 -0
  74. preditor/resource/img/arrow_forward.png +0 -0
  75. preditor/resource/img/check-bold.png +0 -0
  76. preditor/resource/img/chevron-down.png +0 -0
  77. preditor/resource/img/chevron-up.png +0 -0
  78. preditor/resource/img/close-thick.png +0 -0
  79. preditor/resource/img/comment-edit.png +0 -0
  80. preditor/resource/img/content-copy.png +0 -0
  81. preditor/resource/img/content-cut.png +0 -0
  82. preditor/resource/img/content-duplicate.png +0 -0
  83. preditor/resource/img/content-paste.png +0 -0
  84. preditor/resource/img/content-save.png +0 -0
  85. preditor/resource/img/debug_disabled.png +0 -0
  86. preditor/resource/img/eye-check.png +0 -0
  87. preditor/resource/img/file-plus.png +0 -0
  88. preditor/resource/img/file-remove.png +0 -0
  89. preditor/resource/img/format-align-left.png +0 -0
  90. preditor/resource/img/format-letter-case-lower.png +0 -0
  91. preditor/resource/img/format-letter-case-upper.png +0 -0
  92. preditor/resource/img/format-letter-case.svg +1 -0
  93. preditor/resource/img/information.png +0 -0
  94. preditor/resource/img/logging_critical.png +0 -0
  95. preditor/resource/img/logging_custom.png +0 -0
  96. preditor/resource/img/logging_debug.png +0 -0
  97. preditor/resource/img/logging_error.png +0 -0
  98. preditor/resource/img/logging_info.png +0 -0
  99. preditor/resource/img/logging_not_set.png +0 -0
  100. preditor/resource/img/logging_warning.png +0 -0
  101. preditor/resource/img/marker.png +0 -0
  102. preditor/resource/img/play.png +0 -0
  103. preditor/resource/img/playlist-play.png +0 -0
  104. preditor/resource/img/plus-minus-variant.png +0 -0
  105. preditor/resource/img/preditor.ico +0 -0
  106. preditor/resource/img/preditor.png +0 -0
  107. preditor/resource/img/preditor.psd +0 -0
  108. preditor/resource/img/preditor.svg +44 -0
  109. preditor/resource/img/regex.svg +1 -0
  110. preditor/resource/img/restart.svg +1 -0
  111. preditor/resource/img/skip-forward-outline.png +0 -0
  112. preditor/resource/img/skip-next-outline.png +0 -0
  113. preditor/resource/img/skip-next.png +0 -0
  114. preditor/resource/img/skip-previous.png +0 -0
  115. preditor/resource/img/subdirectory-arrow-right.png +0 -0
  116. preditor/resource/img/text-search-variant.png +0 -0
  117. preditor/resource/img/warning-big.png +0 -0
  118. preditor/resource/lang/python.json +30 -0
  119. preditor/resource/pref_updates/pref_updates.json +17 -0
  120. preditor/resource/settings.ini +25 -0
  121. preditor/resource/stylesheet/Bright.css +76 -0
  122. preditor/resource/stylesheet/Dark.css +210 -0
  123. preditor/scintilla/__init__.py +40 -0
  124. preditor/scintilla/delayables/__init__.py +11 -0
  125. preditor/scintilla/delayables/smart_highlight.py +97 -0
  126. preditor/scintilla/delayables/spell_check.py +174 -0
  127. preditor/scintilla/documenteditor.py +1924 -0
  128. preditor/scintilla/finddialog.py +68 -0
  129. preditor/scintilla/lang/__init__.py +80 -0
  130. preditor/scintilla/lang/config/bash.ini +15 -0
  131. preditor/scintilla/lang/config/batch.ini +14 -0
  132. preditor/scintilla/lang/config/cpp.ini +19 -0
  133. preditor/scintilla/lang/config/css.ini +19 -0
  134. preditor/scintilla/lang/config/eyeonscript.ini +17 -0
  135. preditor/scintilla/lang/config/html.ini +21 -0
  136. preditor/scintilla/lang/config/javascript.ini +24 -0
  137. preditor/scintilla/lang/config/lua.ini +16 -0
  138. preditor/scintilla/lang/config/maxscript.ini +20 -0
  139. preditor/scintilla/lang/config/mel.ini +18 -0
  140. preditor/scintilla/lang/config/mu.ini +22 -0
  141. preditor/scintilla/lang/config/nsi.ini +19 -0
  142. preditor/scintilla/lang/config/perl.ini +19 -0
  143. preditor/scintilla/lang/config/puppet.ini +19 -0
  144. preditor/scintilla/lang/config/python.ini +28 -0
  145. preditor/scintilla/lang/config/ruby.ini +19 -0
  146. preditor/scintilla/lang/config/sql.ini +7 -0
  147. preditor/scintilla/lang/config/xml.ini +21 -0
  148. preditor/scintilla/lang/config/yaml.ini +18 -0
  149. preditor/scintilla/lang/language.py +240 -0
  150. preditor/scintilla/lexers/__init__.py +0 -0
  151. preditor/scintilla/lexers/cpplexer.py +22 -0
  152. preditor/scintilla/lexers/javascriptlexer.py +27 -0
  153. preditor/scintilla/lexers/maxscriptlexer.py +235 -0
  154. preditor/scintilla/lexers/mellexer.py +369 -0
  155. preditor/scintilla/lexers/mulexer.py +33 -0
  156. preditor/scintilla/lexers/pythonlexer.py +42 -0
  157. preditor/scintilla/ui/finddialog.ui +160 -0
  158. preditor/settings.py +71 -0
  159. preditor/stream/__init__.py +72 -0
  160. preditor/stream/console_handler.py +169 -0
  161. preditor/stream/director.py +144 -0
  162. preditor/stream/manager.py +97 -0
  163. preditor/streamhandler_helper.py +46 -0
  164. preditor/utils/__init__.py +191 -0
  165. preditor/utils/call_stack.py +86 -0
  166. preditor/utils/cute.py +106 -0
  167. preditor/utils/stylesheets.py +54 -0
  168. preditor/utils/text_search.py +338 -0
  169. preditor/version.py +34 -0
  170. preditor/weakref.py +363 -0
  171. preditor-2.1.0.dist-info/METADATA +308 -0
  172. preditor-2.1.0.dist-info/RECORD +179 -0
  173. preditor-2.1.0.dist-info/WHEEL +5 -0
  174. preditor-2.1.0.dist-info/entry_points.txt +19 -0
  175. preditor-2.1.0.dist-info/licenses/LICENSE +165 -0
  176. preditor-2.1.0.dist-info/top_level.txt +3 -0
  177. tests/encodings/test_ecoding.py +33 -0
  178. tests/find_files/test_find_files.py +74 -0
  179. tests/ide/test_delayable_engine.py +171 -0
@@ -0,0 +1,625 @@
1
+ from __future__ import absolute_import
2
+
3
+ from enum import IntEnum
4
+ from functools import partial
5
+ from pathlib import Path
6
+
7
+ import six
8
+ from Qt.QtCore import QByteArray, QMimeData, QPoint, QRect, Qt
9
+ from Qt.QtGui import QColor, QCursor, QDrag, QPixmap, QRegion
10
+ from Qt.QtWidgets import (
11
+ QApplication,
12
+ QFileDialog,
13
+ QInputDialog,
14
+ QMenu,
15
+ QSizePolicy,
16
+ QTabBar,
17
+ )
18
+
19
+ from preditor import osystem
20
+
21
+ from ..gui import handleMenuHovered
22
+ from ..utils import Truncate
23
+ from ..utils.cute import QtPropertyInit
24
+
25
+
26
+ class TabStates(IntEnum):
27
+ """Nice names for the Tab states for coloring"""
28
+
29
+ Normal = 0
30
+ Linked = 1
31
+ Changed = 2
32
+ ChangedLinked = 3
33
+ Orphaned = 4
34
+ OrphanedLinked = 5
35
+ Dirty = 6
36
+ DirtyLinked = 7
37
+ MissingLinked = 8
38
+
39
+
40
+ class DragTabBar(QTabBar):
41
+ """A QTabBar that allows you to drag and drop its tabs to other DragTabBar's
42
+ while still allowing you to move tabs normally.
43
+
44
+ In most cases you should use `install_tab_widget` to create and add this TabBar
45
+ to a QTabWidget. It takes care of enabling usability features of QTabWidget's.
46
+
47
+ Based on code by ARussel: https://forum.qt.io/post/420469
48
+
49
+ """
50
+
51
+ # the normalColor is set to an invalid color name. When QTabBar.setTabTextColor
52
+ # is called with an invalid color name, it reverts to the QTabBar foreground
53
+ # role instead
54
+ normalColor = QColor("invalidcolor")
55
+
56
+ # These Qt Properties can be customized using style sheets.
57
+ linkedColor = QtPropertyInit('_linkedColor', QColor("grey"))
58
+ missingLinkedColor = QtPropertyInit('_missingLinkedColor', QColor("grey"))
59
+ dirtyColor = QtPropertyInit('_dirtyColor', QColor("grey"))
60
+ dirtyLinkedColor = QtPropertyInit('_dirtyLinkedColor', QColor("grey"))
61
+ changedColor = QtPropertyInit('_changedColor', QColor("grey"))
62
+ changedLinkedColor = QtPropertyInit('_changedLinkedColor', QColor("grey"))
63
+ orphanedColor = QtPropertyInit('_orphanedColor', QColor("grey"))
64
+ orphanedLinkedColor = QtPropertyInit('_orphanedLinkedColor', QColor("grey"))
65
+
66
+ def __init__(self, parent=None, mime_type='DragTabBar'):
67
+ super(DragTabBar, self).__init__(parent=parent)
68
+ self.setAcceptDrops(True)
69
+ self.setMouseTracking(True)
70
+ self._mime_data = None
71
+ self._context_menu_tab = -1
72
+ self.mime_type = mime_type
73
+
74
+ self.fg_color_map = {}
75
+ self.bg_color_map = {}
76
+
77
+ def updateColorMap(self):
78
+ """This cannot be called during __init__, otherwise all bg colors will
79
+ be default, and not read from the style sheet. So instead, the first
80
+ time we need self.bg_color_map, we check if it has values, and call this
81
+ method if it doesn't.
82
+ """
83
+ self.bg_color_map = {
84
+ TabStates.Normal: self.normalColor,
85
+ TabStates.Changed: self.changedColor,
86
+ TabStates.ChangedLinked: self.changedLinkedColor,
87
+ TabStates.Orphaned: self.orphanedColor,
88
+ TabStates.OrphanedLinked: self.orphanedLinkedColor,
89
+ TabStates.Dirty: self.dirtyColor,
90
+ TabStates.DirtyLinked: self.dirtyLinkedColor,
91
+ TabStates.Linked: self.linkedColor,
92
+ TabStates.MissingLinked: self.missingLinkedColor,
93
+ }
94
+ self.fg_color_map = {
95
+ "0": "white",
96
+ "1": "black",
97
+ }
98
+
99
+ def getColorAndToolTip(self, index):
100
+ """Determine the color and tooltip based on the state of the workbox.
101
+
102
+ Args:
103
+ index (int): The index of the tab holding the workbox
104
+
105
+ Returns:
106
+ color, toolTip (QColor, str): The QColor and toolTip string to apply
107
+ to the tab being painted
108
+ """
109
+ state = TabStates.Normal
110
+ toolTip = ""
111
+ if self.parent():
112
+ widget = self.parent().widget(index)
113
+
114
+ filename = None
115
+ if hasattr(widget, "__filename__"):
116
+ filename = widget.__filename__()
117
+
118
+ if widget.__changed_by_instance__():
119
+ if filename:
120
+ state = TabStates.ChangedLinked
121
+ toolTip = (
122
+ "Linked workbox has been updated by saving in another "
123
+ "PrEditor and has had unsaved changes auto-saved to a"
124
+ " previous version.\nAccess with Ctrl-Alt-[ shortcut."
125
+ )
126
+ else:
127
+ state = TabStates.Changed
128
+ toolTip = (
129
+ "Workbox has been updated by saving in another PrEditor "
130
+ "instance, and has had it's unsaved changes auto-saved to "
131
+ "a previous version.\nAccess with Ctrl-Alt-[ shortcut."
132
+ )
133
+ elif widget.__orphaned_by_instance__():
134
+ if filename:
135
+ state = TabStates.OrphanedLinked
136
+ toolTip = (
137
+ "Linked workbox is either newly added, or orphaned by "
138
+ "being removed in another PrEditor instance and saved."
139
+ )
140
+ else:
141
+ state = TabStates.Orphaned
142
+ toolTip = (
143
+ "Workbox is either newly added, or orphaned by "
144
+ "being removed in another PrEditor instance and saved."
145
+ )
146
+ elif widget.__is_dirty__():
147
+ if filename:
148
+ state = TabStates.DirtyLinked
149
+ toolTip = "Linked workbox has unsaved changes."
150
+ else:
151
+ state = TabStates.Dirty
152
+ toolTip = "Workbox has unsaved changes, or it's name has changed."
153
+ elif widget.__is_missing_linked_file__():
154
+ state = TabStates.MissingLinked
155
+ toolTip = "Linked file is missing"
156
+ elif hasattr(widget, "__filename__") and widget.__filename__():
157
+ state = TabStates.Linked
158
+ toolTip = "Linked to file on disk"
159
+
160
+ if hasattr(widget, "__filename__"):
161
+ filename = widget.__filename__()
162
+ if filename:
163
+ toolTip += "\nfilename: {}".format(filename)
164
+
165
+ window = self.window()
166
+ if window.uiExtraTooltipInfoCHK.isChecked():
167
+
168
+ if hasattr(widget, "__workbox_id__"):
169
+ workbox_id = widget.__workbox_id__()
170
+ if toolTip:
171
+ toolTip += "\n\n"
172
+ toolTip += workbox_id
173
+
174
+ toolTip += "\nis dirty: {}".format(widget.__is_dirty__())
175
+ toolTip += "\nstate: {}".format(state.name)
176
+
177
+ if hasattr(widget, "__backup_file__"):
178
+ backup_file = widget.__backup_file__()
179
+ toolTip += "\nBackup file: {}".format(backup_file)
180
+
181
+ if hasattr(widget, "_changed_by_instance"):
182
+ _changed_by_instance = widget._changed_by_instance
183
+ toolTip += "\nHas been changed by instance: {}".format(
184
+ _changed_by_instance
185
+ )
186
+
187
+ if hasattr(widget, "__orphaned_by_instance__"):
188
+ __orphaned_by_instance__ = widget.__orphaned_by_instance__()
189
+ toolTip += "\n __orphaned_by_instance__: {}".format(
190
+ __orphaned_by_instance__
191
+ )
192
+
193
+ if hasattr(widget, "_changed_saved"):
194
+ _changed_saved = widget._changed_saved
195
+ toolTip += "\n _changed_saved: {}".format(_changed_saved)
196
+
197
+ if hasattr(widget, "__last_workbox_name__"):
198
+ last_workbox_name = widget.__last_workbox_name__()
199
+ toolTip += "\nlast_workbox_name: {}".format(last_workbox_name)
200
+
201
+ if hasattr(widget, "__last_saved_text__"):
202
+ last_saved_text = widget.__last_saved_text__()
203
+ last_saved_text = Truncate(last_saved_text).lines()
204
+ toolTip += "\nlast_saved_text: \n{}".format(last_saved_text)
205
+
206
+ color = self.bg_color_map.get(state)
207
+ return color, toolTip
208
+
209
+ def updateColorAndToolTip(self, index):
210
+ """Update the color and tooltip for the tab at index, based of various
211
+ factors about the workbox.
212
+
213
+ Args:
214
+ index (int): The index of the tab to color, and possibly, set toolTip
215
+ """
216
+ self.updateColorMap()
217
+
218
+ color, toolTip = self.getColorAndToolTip(index)
219
+ self.setTabTextColor(index, color)
220
+ self.setTabToolTip(index, toolTip)
221
+
222
+ def updateColorsAndToolTips(self):
223
+ """Update the color and tooltip for all the tabs in this tabBar. Also,
224
+ update the tabBar above it.
225
+ """
226
+ for index in range(self.count()):
227
+ self.updateColorAndToolTip(index)
228
+
229
+ parentIdx = self.window().indexOfWorkboxOrTabGroup(self.parent())
230
+ if parentIdx is not None:
231
+ tabBar = self.parent().__tab_widget__().tabBar()
232
+ tabBar.updateColorAndToolTip(parentIdx)
233
+
234
+ def mouseMoveEvent(self, event): # noqa: N802
235
+ if not self._mime_data:
236
+ return super(DragTabBar, self).mouseMoveEvent(event)
237
+
238
+ # Check if the mouse has moved outside of the widget, if not, let
239
+ # the QTabBar handle the internal tab movement.
240
+ event_pos = event.pos()
241
+ global_pos = self.mapToGlobal(event_pos)
242
+ bar_geo = QRect(self.mapToGlobal(self.pos()), self.size())
243
+ inside = bar_geo.contains(global_pos)
244
+ if inside:
245
+ return super(DragTabBar, self).mouseMoveEvent(event)
246
+
247
+ # The user has moved the tab outside of the QTabBar, remove the tab from
248
+ # this tab bar and store it in the MimeData, initiating a drag event.
249
+ widget = self._mime_data.property('widget')
250
+ tab_index = self.parentWidget().indexOf(widget)
251
+ self.parentWidget().removeTab(tab_index)
252
+ pos_in_tab = self.mapFromGlobal(global_pos)
253
+ drag = QDrag(self)
254
+ drag.setMimeData(self._mime_data)
255
+ drag.setPixmap(self._mime_data.imageData())
256
+ drag.setHotSpot(event_pos - pos_in_tab)
257
+ cursor = QCursor(Qt.CursorShape.OpenHandCursor)
258
+ drag.setDragCursor(cursor.pixmap(), Qt.DropAction.MoveAction)
259
+ action = drag.exec(Qt.DropAction.MoveAction)
260
+ # If the user didn't successfully add this to a new tab widget, restore
261
+ # the tab to the original location.
262
+ if action == Qt.DropAction.IgnoreAction:
263
+ original_tab_index = self._mime_data.property('original_tab_index')
264
+ self.parentWidget().insertTab(
265
+ original_tab_index, widget, self._mime_data.text()
266
+ )
267
+
268
+ self._mime_data = None
269
+
270
+ def mousePressEvent(self, event): # noqa: N802
271
+ if event.button() == Qt.MouseButton.LeftButton and not self._mime_data:
272
+ tab_index = self.tabAt(event.pos())
273
+
274
+ # While we don't remove the tab on mouse press, capture its tab image
275
+ # and attach it to the mouse. This also stores info needed to handle
276
+ # moving the tab to a new QTabWidget, and undoing the move if the
277
+ # user cancels the drop.
278
+ tab_rect = self.tabRect(tab_index)
279
+ pixmap = QPixmap(tab_rect.size())
280
+ self.render(pixmap, QPoint(), QRegion(tab_rect))
281
+
282
+ self._mime_data = QMimeData()
283
+ self._mime_data.setData(self.mime_type, QByteArray())
284
+ self._mime_data.setText(self.tabText(tab_index))
285
+ self._mime_data.setProperty('original_tab_index', tab_index)
286
+ self._mime_data.setImageData(pixmap)
287
+ widget = self.parentWidget().widget(tab_index)
288
+ self._mime_data.setProperty('widget', widget)
289
+
290
+ # By default if there are no tabs, the tab bar is hidden. This
291
+ # prevents users from re-adding tabs to the tab bar as only it
292
+ # accepts the tab drops. This preserves the tab bar height
293
+ # after it was drawn with a tab so it should automatically stay
294
+ # the same visual height.
295
+ if not self.minimumHeight():
296
+ self.setMinimumHeight(self.height())
297
+
298
+ super(DragTabBar, self).mousePressEvent(event)
299
+
300
+ def mouseReleaseEvent(self, event): # noqa: N802
301
+ self._mime_data = None
302
+ super(DragTabBar, self).mouseReleaseEvent(event)
303
+
304
+ def dragEnterEvent(self, event): # noqa: N802
305
+ # if event.mimeData().hasFormat(self.mime_type):
306
+ event.accept()
307
+
308
+ def dragLeaveEvent(self, event): # noqa: N802
309
+ event.accept()
310
+
311
+ def dragMoveEvent(self, event): # noqa: N802
312
+ # If this is not a tab of the same mime type, make the tab under the mouse
313
+ # the current tab so users can easily drop inside that tab.
314
+ if not event.mimeData().hasFormat(self.mime_type):
315
+ event.accept()
316
+ tab_index = self.tabAt(event.pos())
317
+ if tab_index == -1:
318
+ tab_index = self.count() - 1
319
+ if self.currentIndex() != tab_index:
320
+ self.setCurrentIndex(tab_index)
321
+
322
+ def dropEvent(self, event): # noqa: N802
323
+ if not event.mimeData().hasFormat(self.mime_type):
324
+ return
325
+ if event.source().parentWidget() == self:
326
+ return
327
+
328
+ event.setDropAction(Qt.DropAction.MoveAction)
329
+ event.accept()
330
+ counter = self.count()
331
+
332
+ mime_data = event.mimeData()
333
+ if counter == 0:
334
+ self.parent().addTab(mime_data.property('widget'), mime_data.text())
335
+ else:
336
+ self.parent().insertTab(
337
+ counter + 1, mime_data.property('widget'), mime_data.text()
338
+ )
339
+
340
+ def rename_tab(self):
341
+ """Used by the tab_menu to rename the tab at index `_context_menu_tab`."""
342
+ if self._context_menu_tab != -1:
343
+ current = self.tabText(self._context_menu_tab)
344
+ msg = 'Rename the {} tab to (new name must be unique):'.format(current)
345
+
346
+ name, success = QInputDialog.getText(self, 'Rename Tab', msg, text=current)
347
+ name = self.parent().get_next_available_tab_name(name)
348
+ if not name.strip():
349
+ return
350
+
351
+ if success:
352
+ self.setTabText(self._context_menu_tab, name)
353
+ self.updateColorsAndToolTips()
354
+
355
+ def tab_menu(self, pos, popup=True):
356
+ """Creates the custom context menu for the tab bar. To customize the menu
357
+ call super setting `popup=False`. This will return the menu for
358
+ customization and you will then need to call popup on the menu.
359
+
360
+ This method sets the tab index the user right clicked on in the variable
361
+ `_context_menu_tab`. This can be used in the triggered QAction methods."""
362
+
363
+ index = self.tabAt(pos)
364
+ self._context_menu_tab = index
365
+ if self._context_menu_tab == -1:
366
+ return
367
+ menu = QMenu(self)
368
+ menu.setFont(self.window().font())
369
+ menu.hovered.connect(handleMenuHovered)
370
+
371
+ grouped_tab = self.parentWidget()
372
+ workbox = grouped_tab.widget(self._context_menu_tab)
373
+
374
+ # Show File-related actions depending if filename already set. Don't include
375
+ # Rename if the workbox is linked to a file.
376
+ if hasattr(workbox, '__filename__'):
377
+ if not workbox.__filename__():
378
+ act = menu.addAction('Rename')
379
+ tip = "Rename this tab."
380
+ act.setToolTip(tip)
381
+ act.triggered.connect(self.rename_tab)
382
+
383
+ act = menu.addAction('Link File')
384
+ tip = (
385
+ "Choose an existing file on disk to link this workbox to.\n"
386
+ "The current workbox contents will be replaced, but will be "
387
+ "backed up, accessible with Ctrl+Alt+Left"
388
+ )
389
+ act.setToolTip(tip)
390
+ act.triggered.connect(partial(self.link_file, workbox))
391
+
392
+ act = menu.addAction('Save and Link File')
393
+ tip = (
394
+ "Choose a filename to save the workbox contents to, and "
395
+ "link to that file."
396
+ )
397
+ act.setToolTip(tip)
398
+ act.triggered.connect(partial(self.save_and_link_file, workbox))
399
+ else:
400
+ if Path(workbox.__filename__()).is_file():
401
+ act = menu.addAction('Explore File')
402
+ tip = "Open a file explorer at the linked file's location"
403
+ act.setToolTip(tip)
404
+ act.triggered.connect(partial(self.explore_file, workbox))
405
+
406
+ act = menu.addAction('Unlink File')
407
+ tip = (
408
+ "Disconnect the link to the file on disk. The workbox "
409
+ "contents will remain unchanged."
410
+ )
411
+ act.setToolTip(tip)
412
+ act.triggered.connect(partial(self.unlink_file, workbox))
413
+
414
+ act = menu.addAction('Save As')
415
+ tip = (
416
+ "Save contents as a new file and link to it. If you "
417
+ "choose an existing file, that file's contents will be "
418
+ "overwritten."
419
+ )
420
+ act.setToolTip(tip)
421
+ act.triggered.connect(partial(self.save_and_link_file, workbox))
422
+
423
+ act = menu.addAction('Copy Filename')
424
+ tip = "Copy this workbox's filename to the clipboard."
425
+ act.setToolTip(tip)
426
+ act.triggered.connect(partial(self.copyFilename, workbox))
427
+ else:
428
+ act = menu.addAction('Explore File')
429
+ tip = "Open a file explorer at the linked file's location"
430
+ act.setToolTip(tip)
431
+ act.triggered.connect(partial(self.explore_file, workbox))
432
+
433
+ act = menu.addAction('Save As and Link File')
434
+ tip = (
435
+ "Save contents as a new file and link to it. If you "
436
+ "choose an existing file, that file's contents will be "
437
+ "overwritten."
438
+ )
439
+ act.setToolTip(tip)
440
+ act.triggered.connect(partial(self.save_and_link_file, workbox))
441
+
442
+ act = menu.addAction('Relink File')
443
+ tip = (
444
+ "Current linked file is not found. Choose a new file "
445
+ "to link to."
446
+ )
447
+ act.setToolTip(tip)
448
+ act.triggered.connect(
449
+ partial(self.link_file, workbox, saveLinkedFile=False)
450
+ )
451
+
452
+ act = menu.addAction('Unlink File')
453
+ tip = (
454
+ "Disconnect the link to the file on disk. The workbox "
455
+ "contents will remain unchanged."
456
+ )
457
+ act.setToolTip(tip)
458
+ act.triggered.connect(partial(self.unlink_file, workbox))
459
+ else:
460
+ act = menu.addAction('Rename')
461
+ tip = "Rename this tab."
462
+ act.setToolTip(tip)
463
+ act.triggered.connect(self.rename_tab)
464
+
465
+ act = menu.addAction('Copy Workbox Name')
466
+ tip = "Copy this workbox's name to the clipboard."
467
+ act.setToolTip(tip)
468
+ act.triggered.connect(partial(self.copy_workbox_name, workbox, index))
469
+
470
+ act = menu.addAction('Copy Workbox Id')
471
+ tip = "Copy this workbox's id to the clipboard."
472
+ act.setToolTip(tip)
473
+ act.triggered.connect(partial(self.copy_workbox_id, workbox, index))
474
+
475
+ if popup:
476
+ menu.popup(self.mapToGlobal(pos))
477
+
478
+ return menu
479
+
480
+ def link_file(self, workbox, saveLinkedFile=True):
481
+ """Link the given workbox to a file on disk.
482
+
483
+ Args:
484
+ workbox (WorkboxMixin): The workbox contained in the clicked tab
485
+ """
486
+ filename = workbox.__filename__()
487
+ filename, _other = QFileDialog.getOpenFileName(directory=filename)
488
+ if filename and Path(filename).is_file():
489
+ workbox.__set_file_monitoring_enabled__(False)
490
+
491
+ # First, save any unsaved text
492
+ workbox.__save_prefs__(saveLinkedFile=saveLinkedFile)
493
+
494
+ # Now, load file
495
+ workbox.__load__(filename)
496
+ workbox.__set_filename__(filename)
497
+ workbox.__set_file_monitoring_enabled__(True)
498
+
499
+ name = Path(filename).name
500
+ self.setTabText(self._context_menu_tab, name)
501
+ self.updateColorsAndToolTips()
502
+ self.update()
503
+ self.window().setWorkboxFontBasedOnConsole(workbox=workbox)
504
+
505
+ workbox.__save_prefs__(saveLinkedFile=False, force=True)
506
+
507
+ def save_and_link_file(self, workbox):
508
+ """Save the given workbox as a file on disk, and link the workbox to it.
509
+
510
+ Args:
511
+ workbox (WorkboxMixin): The workbox contained in the clicked tab
512
+ """
513
+ filename = workbox.__filename__()
514
+ if filename and Path(filename).is_file():
515
+ workbox.__set_file_monitoring_enabled__(False)
516
+ directory = six.text_type(Path(filename).parent) if filename else ""
517
+ success = workbox.__save_as__(directory=directory)
518
+ if not success:
519
+ return
520
+
521
+ # Workbox
522
+ filename = workbox.__filename__()
523
+ workbox.__set_last_saved_text__(workbox.__text__())
524
+ workbox.__set_file_monitoring_enabled__(True)
525
+ name = Path(filename).name
526
+
527
+ self.setTabText(self._context_menu_tab, name)
528
+ self.updateColorsAndToolTips()
529
+ self.update()
530
+ self.window().setWorkboxFontBasedOnConsole(workbox=workbox)
531
+ workbox.__set_last_workbox_name__(workbox.__workbox_name__())
532
+
533
+ def explore_file(self, workbox):
534
+ """Open a system file explorer at the path of the linked file.
535
+
536
+ Args:
537
+ workbox (WorkboxMixin): The workbox contained in the clicked tab
538
+ """
539
+ path = Path(workbox.__filename__())
540
+ if path.exists():
541
+ osystem.explore(str(path))
542
+ elif path.parent.exists():
543
+ osystem.explore(str(path.parent))
544
+
545
+ def unlink_file(self, workbox):
546
+ """Disconnect a file link.
547
+
548
+ Args:
549
+ workbox (WorkboxMixin): The workbox contained in the clicked tab
550
+ """
551
+ workbox.__set_file_monitoring_enabled__(False)
552
+ workbox.__set_filename__("")
553
+ name = self.parent().default_title
554
+ self.setTabText(self._context_menu_tab, name)
555
+ self.updateColorsAndToolTips()
556
+
557
+ def copyFilename(self, workbox):
558
+ """Copy the given workbox's filename to the clipboard
559
+
560
+ Args:
561
+ workbox (WorkboxMixin): The workbox for which to provide the filename
562
+ """
563
+ filename = workbox.__filename__()
564
+ QApplication.clipboard().setText(filename)
565
+
566
+ def copy_workbox_name(self, workbox, index):
567
+ """Copy the workbox name to clipboard.
568
+
569
+ Args:
570
+ workbox (WorkboxMixin): The workbox contained in the clicked tab
571
+ index (index): The index of the clicked tab
572
+ """
573
+ try:
574
+ name = workbox.__workbox_name__()
575
+ except AttributeError:
576
+ group = self.parent().widget(index)
577
+ curIndex = group.currentIndex()
578
+ workbox = group.widget(curIndex)
579
+ name = workbox.__workbox_name__()
580
+ QApplication.clipboard().setText(name)
581
+
582
+ def copy_workbox_id(self, workbox, index):
583
+ """Copy the workbox id to clipboard.
584
+
585
+ Args:
586
+ workbox (WorkboxMixin): The workbox contained in the clicked tab
587
+ index (index): The index of the clicked tab
588
+ """
589
+ try:
590
+ workbox_id = workbox.__workbox_id__()
591
+ except AttributeError:
592
+ group = self.parent().widget(index)
593
+ curIndex = group.currentIndex()
594
+ workbox = group.widget(curIndex)
595
+ workbox_id = workbox.__workbox_id__()
596
+ QApplication.clipboard().setText(workbox_id)
597
+
598
+ @classmethod
599
+ def install_tab_widget(cls, tab_widget, mime_type='DragTabBar', menu=True):
600
+ """Creates and returns a instance of DragTabBar and installs it on the
601
+ QTabWidget. This enables movable tabs, and enables document mode.
602
+ Document mode makes the tab bar expand to the size of the QTabWidget so
603
+ drag drop operations are more intuitive.
604
+
605
+ Args:
606
+ tab_widget (QTabWidget): The QTabWidget to install the tab bar on.
607
+ mime_data (str, optional): This TabBar will only accept tab drop
608
+ operations with this mime type.
609
+ menu (bool, optional): Install a custom context menu on the bar bar.
610
+ Override `tab_menu` to customize the menu.
611
+ """
612
+ bar = cls(tab_widget, mime_type=mime_type)
613
+ tab_widget.setTabBar(bar)
614
+ tab_widget.setMovable(True)
615
+ tab_widget.setDocumentMode(True)
616
+
617
+ sizePolicy = tab_widget.sizePolicy()
618
+ sizePolicy.setVerticalPolicy(QSizePolicy.Policy.Preferred)
619
+ tab_widget.setSizePolicy(sizePolicy)
620
+
621
+ if menu:
622
+ bar.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
623
+ bar.customContextMenuRequested.connect(bar.tab_menu)
624
+
625
+ return bar
@@ -0,0 +1,57 @@
1
+ from __future__ import absolute_import
2
+
3
+ from Qt.QtCore import Slot
4
+ from Qt.QtGui import QIcon
5
+ from Qt.QtWidgets import QWidget
6
+
7
+ from .. import plugins, resourcePath
8
+ from ..gui import loadUi
9
+
10
+
11
+ class EditorChooser(QWidget):
12
+ """A widget that lets the user choose from a a list of available editors."""
13
+
14
+ def __init__(self, parent=None, editor_name=None):
15
+ super(EditorChooser, self).__init__(parent=parent)
16
+ loadUi(__file__, self)
17
+ icon = QIcon(resourcePath('img/warning-big.png'))
18
+ self.uiWarningIconLBL.setPixmap(icon.pixmap(icon.availableSizes()[0]))
19
+ if editor_name:
20
+ self.set_editor_name(editor_name)
21
+
22
+ def editor_name(self):
23
+ return self.uiWorkboxEditorDDL.currentText()
24
+
25
+ def set_editor_name(self, name):
26
+ index = self.uiWorkboxEditorDDL.findText(name)
27
+ if index == -1:
28
+ self.uiWorkboxEditorDDL.addItem(name)
29
+ index = self.uiWorkboxEditorDDL.findText(name)
30
+ self.uiWorkboxEditorDDL.setCurrentIndex(index)
31
+
32
+ @Slot()
33
+ def refresh(self):
34
+ warning = "Choose an editor to enable Workboxs."
35
+ editor_name = self.editor_name()
36
+ if editor_name:
37
+ _, editor = plugins.editor(editor_name)
38
+ warning = editor._warning_text
39
+ self.uiWarningIconLBL.setVisible(bool(warning))
40
+ self.uiWarningTextLBL.setVisible(bool(warning))
41
+ self.uiWarningTextLBL.setText(warning)
42
+
43
+ def refresh_editors(self):
44
+ current = self.editor_name()
45
+ self.uiWorkboxEditorDDL.blockSignals(True)
46
+ self.uiWorkboxEditorDDL.clear()
47
+ for name, _ in sorted(set(plugins.editors())):
48
+ self.uiWorkboxEditorDDL.addItem(name)
49
+
50
+ self.uiWorkboxEditorDDL.setCurrentIndex(
51
+ self.uiWorkboxEditorDDL.findText(current)
52
+ )
53
+ self.uiWorkboxEditorDDL.blockSignals(False)
54
+
55
+ def showEvent(self, event): # noqa: N802
56
+ super(EditorChooser, self).showEvent(event)
57
+ self.refresh_editors()