PrEditor 0.0.0.dev1__py2.py3-none-any.whl → 0.1.0__py2.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.

Potentially problematic release.


This version of PrEditor might be problematic. Click here for more details.

Files changed (363) hide show
  1. PrEditor-0.1.0.dist-info/LICENSE +165 -0
  2. PrEditor-0.1.0.dist-info/METADATA +212 -0
  3. PrEditor-0.1.0.dist-info/RECORD +149 -0
  4. {PrEditor-0.0.0.dev1.dist-info → PrEditor-0.1.0.dist-info}/WHEEL +1 -1
  5. PrEditor-0.1.0.dist-info/entry_points.txt +18 -0
  6. PrEditor-0.1.0.dist-info/top_level.txt +1 -0
  7. preditor/__init__.py +301 -0
  8. {blurdev → preditor}/__main__.py +13 -13
  9. preditor/about_module.py +166 -0
  10. preditor/cli.py +192 -0
  11. {blurdev → preditor}/contexts.py +119 -119
  12. preditor/cores/core.py +65 -0
  13. preditor/dccs/maya/PrEditor_maya.mod +2 -0
  14. preditor/dccs/maya/plug-ins/PrEditor_maya.py +108 -0
  15. preditor/debug.py +294 -0
  16. blurdev/scintilla/delayable_engine.py → preditor/delayable_engine/__init__.py +310 -299
  17. blurdev/scintilla/delayables/base.py → preditor/delayable_engine/delayables.py +85 -85
  18. {blurdev → preditor}/enum.py +728 -1003
  19. {blurdev → preditor}/gui/__init__.py +84 -125
  20. preditor/gui/app.py +159 -0
  21. {blurdev → preditor}/gui/codehighlighter.py +209 -219
  22. {blurdev → preditor}/gui/completer.py +226 -236
  23. {blurdev → preditor}/gui/console.py +801 -858
  24. {blurdev → preditor}/gui/dialog.py +200 -216
  25. preditor/gui/drag_tab_bar.py +190 -0
  26. preditor/gui/editor_chooser.py +57 -0
  27. {blurdev → preditor}/gui/errordialog.py +100 -97
  28. preditor/gui/fuzzy_search/fuzzy_search.py +93 -0
  29. preditor/gui/group_tab_widget/__init__.py +319 -0
  30. preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
  31. preditor/gui/group_tab_widget/grouped_tab_models.py +108 -0
  32. preditor/gui/group_tab_widget/grouped_tab_widget.py +75 -0
  33. preditor/gui/group_tab_widget/one_tab_widget.py +54 -0
  34. preditor/gui/level_buttons.py +349 -0
  35. {blurdev → preditor}/gui/logger_window_handler.py +46 -45
  36. {blurdev → preditor}/gui/loggerwindow.py +1205 -1417
  37. {blurdev → preditor}/gui/newtabwidget.py +69 -68
  38. {blurdev → preditor}/gui/redmine_login_dialog.py +63 -61
  39. {blurdev → preditor}/gui/set_text_editor_path_dialog.py +59 -57
  40. preditor/gui/ui/editor_chooser.ui +93 -0
  41. {blurdev → preditor}/gui/ui/errordialog.ui +81 -81
  42. {blurdev → preditor}/gui/ui/loggerwindow.ui +1030 -864
  43. {blurdev → preditor}/gui/ui/redmine_login_dialog.ui +124 -124
  44. {blurdev → preditor}/gui/ui/set_text_editor_path_dialog.ui +149 -149
  45. {blurdev → preditor}/gui/window.py +183 -199
  46. preditor/gui/workbox_mixin.py +357 -0
  47. preditor/gui/workbox_text_edit.py +117 -0
  48. preditor/gui/workboxwidget.py +276 -0
  49. preditor/logging_config.py +52 -0
  50. preditor/osystem.py +401 -0
  51. preditor/plugins.py +65 -0
  52. preditor/prefs.py +74 -0
  53. {blurdev → preditor}/resource/environment_variables.html +26 -38
  54. {blurdev → preditor}/resource/error_mail.html +85 -85
  55. {blurdev → preditor}/resource/error_mail_inline.html +41 -41
  56. preditor/resource/img/README.md +7 -0
  57. preditor/resource/img/arrow_forward.png +0 -0
  58. preditor/resource/img/check-bold.png +0 -0
  59. preditor/resource/img/chevron-down.png +0 -0
  60. preditor/resource/img/chevron-up.png +0 -0
  61. preditor/resource/img/close-thick.png +0 -0
  62. preditor/resource/img/comment-edit.png +0 -0
  63. preditor/resource/img/content-copy.png +0 -0
  64. preditor/resource/img/content-cut.png +0 -0
  65. preditor/resource/img/content-duplicate.png +0 -0
  66. preditor/resource/img/content-paste.png +0 -0
  67. preditor/resource/img/content-save.png +0 -0
  68. preditor/resource/img/debug_disabled.png +0 -0
  69. preditor/resource/img/eye-check.png +0 -0
  70. preditor/resource/img/file-plus.png +0 -0
  71. preditor/resource/img/file-remove.png +0 -0
  72. preditor/resource/img/format-align-left.png +0 -0
  73. preditor/resource/img/format-letter-case-lower.png +0 -0
  74. preditor/resource/img/format-letter-case-upper.png +0 -0
  75. preditor/resource/img/information.png +0 -0
  76. preditor/resource/img/logging_critical.png +0 -0
  77. preditor/resource/img/logging_custom.png +0 -0
  78. preditor/resource/img/logging_debug.png +0 -0
  79. preditor/resource/img/logging_error.png +0 -0
  80. preditor/resource/img/logging_info.png +0 -0
  81. preditor/resource/img/logging_not_set.png +0 -0
  82. preditor/resource/img/logging_warning.png +0 -0
  83. preditor/resource/img/marker.png +0 -0
  84. preditor/resource/img/play.png +0 -0
  85. preditor/resource/img/playlist-play.png +0 -0
  86. preditor/resource/img/plus-minus-variant.png +0 -0
  87. preditor/resource/img/preditor.ico +0 -0
  88. preditor/resource/img/preditor.png +0 -0
  89. preditor/resource/img/preditor.psd +0 -0
  90. preditor/resource/img/preditor.svg +44 -0
  91. preditor/resource/img/restart.svg +1 -0
  92. preditor/resource/img/skip-forward-outline.png +0 -0
  93. preditor/resource/img/skip-next-outline.png +0 -0
  94. preditor/resource/img/skip-next.png +0 -0
  95. preditor/resource/img/skip-previous.png +0 -0
  96. preditor/resource/img/subdirectory-arrow-right.png +0 -0
  97. preditor/resource/img/text-search-variant.png +0 -0
  98. {blurdev → preditor}/resource/lang/python.json +30 -30
  99. preditor/resource/settings.ini +25 -0
  100. {blurdev/resource/stylesheet/logger → preditor/resource/stylesheet}/Bright.css +56 -61
  101. {blurdev → preditor}/resource/stylesheet/Dark.css +190 -132
  102. {blurdev → preditor}/scintilla/__init__.py +22 -28
  103. preditor/scintilla/delayables/__init__.py +11 -0
  104. {blurdev → preditor}/scintilla/delayables/smart_highlight.py +94 -93
  105. {blurdev → preditor}/scintilla/delayables/spell_check.py +173 -172
  106. {blurdev → preditor}/scintilla/documenteditor.py +2039 -2115
  107. {blurdev → preditor}/scintilla/finddialog.py +68 -81
  108. {blurdev → preditor}/scintilla/lang/__init__.py +80 -93
  109. {blurdev → preditor}/scintilla/lang/config/bash.ini +15 -15
  110. {blurdev → preditor}/scintilla/lang/config/batch.ini +14 -14
  111. {blurdev → preditor}/scintilla/lang/config/cpp.ini +19 -19
  112. {blurdev → preditor}/scintilla/lang/config/css.ini +19 -19
  113. {blurdev → preditor}/scintilla/lang/config/eyeonscript.ini +17 -17
  114. {blurdev → preditor}/scintilla/lang/config/html.ini +21 -21
  115. {blurdev → preditor}/scintilla/lang/config/javascript.ini +24 -24
  116. {blurdev → preditor}/scintilla/lang/config/lua.ini +16 -16
  117. {blurdev → preditor}/scintilla/lang/config/maxscript.ini +20 -20
  118. {blurdev → preditor}/scintilla/lang/config/mel.ini +18 -18
  119. {blurdev → preditor}/scintilla/lang/config/mu.ini +22 -22
  120. {blurdev → preditor}/scintilla/lang/config/nsi.ini +5 -5
  121. {blurdev → preditor}/scintilla/lang/config/perl.ini +19 -19
  122. {blurdev → preditor}/scintilla/lang/config/puppet.ini +19 -19
  123. {blurdev → preditor}/scintilla/lang/config/python.ini +28 -28
  124. {blurdev → preditor}/scintilla/lang/config/ruby.ini +19 -19
  125. {blurdev → preditor}/scintilla/lang/config/sql.ini +7 -7
  126. {blurdev → preditor}/scintilla/lang/config/xml.ini +21 -21
  127. {blurdev → preditor}/scintilla/lang/config/yaml.ini +18 -18
  128. {blurdev → preditor}/scintilla/lang/language.py +240 -250
  129. preditor/scintilla/lexers/__init__.py +0 -0
  130. {blurdev → preditor}/scintilla/lexers/cpplexer.py +21 -30
  131. {blurdev → preditor}/scintilla/lexers/javascriptlexer.py +25 -34
  132. {blurdev → preditor}/scintilla/lexers/maxscriptlexer.py +234 -253
  133. {blurdev → preditor}/scintilla/lexers/mellexer.py +368 -376
  134. {blurdev → preditor}/scintilla/lexers/mulexer.py +32 -41
  135. {blurdev → preditor}/scintilla/lexers/pythonlexer.py +41 -50
  136. {blurdev → preditor}/scintilla/ui/finddialog.ui +160 -160
  137. preditor/settings.py +71 -0
  138. preditor/stream/__init__.py +80 -0
  139. preditor/stream/director.py +56 -0
  140. preditor/stream/manager.py +74 -0
  141. preditor/streamhandler_helper.py +46 -0
  142. preditor/utils/__init__.py +0 -0
  143. preditor/utils/cute.py +30 -0
  144. preditor/utils/stylesheets.py +54 -0
  145. {blurdev → preditor}/version.py +5 -5
  146. preditor/weakref.py +363 -0
  147. PrEditor-0.0.0.dev1.dist-info/METADATA +0 -51
  148. PrEditor-0.0.0.dev1.dist-info/RECORD +0 -279
  149. PrEditor-0.0.0.dev1.dist-info/top_level.txt +0 -1
  150. blurdev/__init__.py +0 -356
  151. blurdev/cores/__init__.py +0 -98
  152. blurdev/cores/application.py +0 -26
  153. blurdev/cores/core.py +0 -634
  154. blurdev/debug.py +0 -593
  155. blurdev/external.py +0 -391
  156. blurdev/gui/level_buttons.py +0 -585
  157. blurdev/gui/workboxwidget.py +0 -205
  158. blurdev/logger.py +0 -238
  159. blurdev/osystem.py +0 -813
  160. blurdev/prefs.py +0 -33
  161. blurdev/protocols/__init__.py +0 -71
  162. blurdev/protocols/write_std_output_handler.py +0 -83
  163. blurdev/resource/designer_plugins.xml +0 -9
  164. blurdev/resource/error_email_old.html +0 -41
  165. blurdev/resource/img/add.png +0 -0
  166. blurdev/resource/img/ajax-loader.gif +0 -0
  167. blurdev/resource/img/application.png +0 -0
  168. blurdev/resource/img/applications.png +0 -0
  169. blurdev/resource/img/assburner.png +0 -0
  170. blurdev/resource/img/assfreezer.png +0 -0
  171. blurdev/resource/img/bar.gif +0 -0
  172. blurdev/resource/img/blank.png +0 -0
  173. blurdev/resource/img/blurdev.png +0 -0
  174. blurdev/resource/img/calendar_disabled.png +0 -0
  175. blurdev/resource/img/calendar_enabled.png +0 -0
  176. blurdev/resource/img/cancel.png +0 -0
  177. blurdev/resource/img/custom.png +0 -0
  178. blurdev/resource/img/debug_high.png +0 -0
  179. blurdev/resource/img/debug_low.png +0 -0
  180. blurdev/resource/img/debug_mid.png +0 -0
  181. blurdev/resource/img/debug_off.png +0 -0
  182. blurdev/resource/img/django.png +0 -0
  183. blurdev/resource/img/doc.png +0 -0
  184. blurdev/resource/img/edit.png +0 -0
  185. blurdev/resource/img/elemental.png +0 -0
  186. blurdev/resource/img/explore.png +0 -0
  187. blurdev/resource/img/favorite.png +0 -0
  188. blurdev/resource/img/file.png +0 -0
  189. blurdev/resource/img/folder.png +0 -0
  190. blurdev/resource/img/ide/add.png +0 -0
  191. blurdev/resource/img/ide/add_note.png +0 -0
  192. blurdev/resource/img/ide/arrow_down.png +0 -0
  193. blurdev/resource/img/ide/arrow_up.png +0 -0
  194. blurdev/resource/img/ide/check.png +0 -0
  195. blurdev/resource/img/ide/class.png +0 -0
  196. blurdev/resource/img/ide/clean.png +0 -0
  197. blurdev/resource/img/ide/clearlog.png +0 -0
  198. blurdev/resource/img/ide/close.png +0 -0
  199. blurdev/resource/img/ide/comment_add.png +0 -0
  200. blurdev/resource/img/ide/comment_remove.png +0 -0
  201. blurdev/resource/img/ide/comment_toggle.png +0 -0
  202. blurdev/resource/img/ide/console.png +0 -0
  203. blurdev/resource/img/ide/copy.png +0 -0
  204. blurdev/resource/img/ide/copylstrip.png +0 -0
  205. blurdev/resource/img/ide/cut.png +0 -0
  206. blurdev/resource/img/ide/edit.png +0 -0
  207. blurdev/resource/img/ide/find.png +0 -0
  208. blurdev/resource/img/ide/find_replace.png +0 -0
  209. blurdev/resource/img/ide/findnext.png +0 -0
  210. blurdev/resource/img/ide/findprev.png +0 -0
  211. blurdev/resource/img/ide/folder_find.png +0 -0
  212. blurdev/resource/img/ide/function.png +0 -0
  213. blurdev/resource/img/ide/git-bash.png +0 -0
  214. blurdev/resource/img/ide/git-gui.png +0 -0
  215. blurdev/resource/img/ide/gitk.png +0 -0
  216. blurdev/resource/img/ide/goto.png +0 -0
  217. blurdev/resource/img/ide/goto_def.png +0 -0
  218. blurdev/resource/img/ide/help.png +0 -0
  219. blurdev/resource/img/ide/highlighter.png +0 -0
  220. blurdev/resource/img/ide/lowercase.png +0 -0
  221. blurdev/resource/img/ide/newfile.png +0 -0
  222. blurdev/resource/img/ide/newfolder.png +0 -0
  223. blurdev/resource/img/ide/newproject.png +0 -0
  224. blurdev/resource/img/ide/newwizard.png +0 -0
  225. blurdev/resource/img/ide/open.png +0 -0
  226. blurdev/resource/img/ide/paste.png +0 -0
  227. blurdev/resource/img/ide/pdb_continue.png +0 -0
  228. blurdev/resource/img/ide/pdb_down.png +0 -0
  229. blurdev/resource/img/ide/pdb_next.png +0 -0
  230. blurdev/resource/img/ide/pdb_step.png +0 -0
  231. blurdev/resource/img/ide/pdb_up.png +0 -0
  232. blurdev/resource/img/ide/plus_minus.png +0 -0
  233. blurdev/resource/img/ide/preferences.png +0 -0
  234. blurdev/resource/img/ide/project_find.png +0 -0
  235. blurdev/resource/img/ide/python.png +0 -0
  236. blurdev/resource/img/ide/pyular.png +0 -0
  237. blurdev/resource/img/ide/qt.png +0 -0
  238. blurdev/resource/img/ide/quit.png +0 -0
  239. blurdev/resource/img/ide/redo.png +0 -0
  240. blurdev/resource/img/ide/refresh.png +0 -0
  241. blurdev/resource/img/ide/remove.png +0 -0
  242. blurdev/resource/img/ide/ruler.png +0 -0
  243. blurdev/resource/img/ide/run.png +0 -0
  244. blurdev/resource/img/ide/runall.png +0 -0
  245. blurdev/resource/img/ide/runallclear.png +0 -0
  246. blurdev/resource/img/ide/runselected.png +0 -0
  247. blurdev/resource/img/ide/runselectedclear.png +0 -0
  248. blurdev/resource/img/ide/save.png +0 -0
  249. blurdev/resource/img/ide/saveas.png +0 -0
  250. blurdev/resource/img/ide/sdk.png +0 -0
  251. blurdev/resource/img/ide/separator.png +0 -0
  252. blurdev/resource/img/ide/tabbed.png +0 -0
  253. blurdev/resource/img/ide/tile.png +0 -0
  254. blurdev/resource/img/ide/toolbar.png +0 -0
  255. blurdev/resource/img/ide/undo.png +0 -0
  256. blurdev/resource/img/ide/uppercase.png +0 -0
  257. blurdev/resource/img/ide/view_as.png +0 -0
  258. blurdev/resource/img/ide/windowed.png +0 -0
  259. blurdev/resource/img/ide.ico +0 -0
  260. blurdev/resource/img/ide.png +0 -0
  261. blurdev/resource/img/ide48.png +0 -0
  262. blurdev/resource/img/info.png +0 -0
  263. blurdev/resource/img/legacy tool.png +0 -0
  264. blurdev/resource/img/library.png +0 -0
  265. blurdev/resource/img/logger/about.png +0 -0
  266. blurdev/resource/img/logger/arrow_forward.png +0 -0
  267. blurdev/resource/img/logger/clear.png +0 -0
  268. blurdev/resource/img/logger/close.png +0 -0
  269. blurdev/resource/img/logger/debug_disabled.png +0 -0
  270. blurdev/resource/img/logger/debug_high.png +0 -0
  271. blurdev/resource/img/logger/debug_low.png +0 -0
  272. blurdev/resource/img/logger/debug_mid.png +0 -0
  273. blurdev/resource/img/logger/down.png +0 -0
  274. blurdev/resource/img/logger/find.png +0 -0
  275. blurdev/resource/img/logger/logging_critical.png +0 -0
  276. blurdev/resource/img/logger/logging_debug.png +0 -0
  277. blurdev/resource/img/logger/logging_error.png +0 -0
  278. blurdev/resource/img/logger/logging_info.png +0 -0
  279. blurdev/resource/img/logger/logging_not_set.png +0 -0
  280. blurdev/resource/img/logger/logging_warning.png +0 -0
  281. blurdev/resource/img/logger/next.png +0 -0
  282. blurdev/resource/img/logger/play.png +0 -0
  283. blurdev/resource/img/logger/playlist_play.png +0 -0
  284. blurdev/resource/img/logger/previous.png +0 -0
  285. blurdev/resource/img/logger/return.png +0 -0
  286. blurdev/resource/img/logger/save.png +0 -0
  287. blurdev/resource/img/logger/subdirectory_arrow_right.png +0 -0
  288. blurdev/resource/img/logger/up.png +0 -0
  289. blurdev/resource/img/lovebar.png +0 -0
  290. blurdev/resource/img/new.png +0 -0
  291. blurdev/resource/img/new_selected.png +0 -0
  292. blurdev/resource/img/node.png +0 -0
  293. blurdev/resource/img/ok.png +0 -0
  294. blurdev/resource/img/options.png +0 -0
  295. blurdev/resource/img/packages.png +0 -0
  296. blurdev/resource/img/preview/add.png +0 -0
  297. blurdev/resource/img/preview/brush.png +0 -0
  298. blurdev/resource/img/preview/delete.png +0 -0
  299. blurdev/resource/img/preview/delte.png +0 -0
  300. blurdev/resource/img/preview/fill.png +0 -0
  301. blurdev/resource/img/preview/layers.png +0 -0
  302. blurdev/resource/img/preview/media.png +0 -0
  303. blurdev/resource/img/preview/navigate.png +0 -0
  304. blurdev/resource/img/preview/pencil.png +0 -0
  305. blurdev/resource/img/preview/select.png +0 -0
  306. blurdev/resource/img/preview/type.png +0 -0
  307. blurdev/resource/img/preview/visible.png +0 -0
  308. blurdev/resource/img/project.png +0 -0
  309. blurdev/resource/img/python_logger.ico +0 -0
  310. blurdev/resource/img/python_logger.png +0 -0
  311. blurdev/resource/img/refresh.png +0 -0
  312. blurdev/resource/img/remove.png +0 -0
  313. blurdev/resource/img/reset.png +0 -0
  314. blurdev/resource/img/richtext/font_bold.png +0 -0
  315. blurdev/resource/img/richtext/font_italic.png +0 -0
  316. blurdev/resource/img/richtext/link_image.png +0 -0
  317. blurdev/resource/img/richtext/spell_check.png +0 -0
  318. blurdev/resource/img/richtext/unordered_list.png +0 -0
  319. blurdev/resource/img/save.png +0 -0
  320. blurdev/resource/img/savesettings.png +0 -0
  321. blurdev/resource/img/settings.png +0 -0
  322. blurdev/resource/img/tool.png +0 -0
  323. blurdev/resource/img/toolbarHandleHorizontal.png +0 -0
  324. blurdev/resource/img/toolbarHandleVertical.png +0 -0
  325. blurdev/resource/img/trash.png +0 -0
  326. blurdev/resource/img/trax.png +0 -0
  327. blurdev/resource/img/tree.png +0 -0
  328. blurdev/resource/img/treegrunt.ico +0 -0
  329. blurdev/resource/img/treegrunt.png +0 -0
  330. blurdev/resource/img/treegruntedit.png +0 -0
  331. blurdev/resource/img/user interface.png +0 -0
  332. blurdev/resource/img/warning.png +0 -0
  333. blurdev/resource/img/watermark.png +0 -0
  334. blurdev/resource/sdk/blurdev.sdk +0 -3
  335. blurdev/resource/settings.ini +0 -82
  336. blurdev/resource/softimage/BlurApplication.dll +0 -0
  337. blurdev/resource/softimage/BlurApplication64.dll +0 -0
  338. blurdev/resource/stylesheet/Carbon.css +0 -35
  339. blurdev/resource/stylesheet/logger/Dark.css +0 -62
  340. blurdev/resource/templ/py_comment.templ +0 -1
  341. blurdev/resource/templ/py_debug_raise_error.templ +0 -7
  342. blurdev/resource/templ/py_doc_string.templ +0 -10
  343. blurdev/resource/templ/py_header.templ +0 -9
  344. blurdev/resource/templ/py_line_comment.templ +0 -1
  345. blurdev/resource/templ/py_log_to_file.templ +0 -22
  346. blurdev/resource/templ/py_module_path.templ +0 -1
  347. blurdev/resource/templ/py_pyqt_core.templ +0 -1
  348. blurdev/resource/templ/py_pyqt_gui.templ +0 -1
  349. blurdev/resource/templ/py_splashscreen.templ +0 -6
  350. blurdev/resource/templ/py_testing_note.templ +0 -1
  351. blurdev/resource/templ/py_testing_note_end.templ +0 -1
  352. blurdev/resource/tools_environments.json +0 -72
  353. blurdev/resource/tools_environments.xml +0 -11
  354. blurdev/resource/tools_environments_linux.xml +0 -11
  355. blurdev/resource/tools_environments_offline.xml +0 -7
  356. blurdev/runtimes/__init__.py +0 -2
  357. blurdev/runtimes/logger.py +0 -44
  358. blurdev/scintilla/delayables/__init__.py +0 -9
  359. blurdev/settings.py +0 -312
  360. blurdev/utils/error.py +0 -389
  361. {blurdev/scintilla/lexers → preditor/cores}/__init__.py +0 -0
  362. {blurdev/utils → preditor/gui/fuzzy_search}/__init__.py +0 -0
  363. {blurdev → preditor}/resource/img/warning-big.png +0 -0
@@ -1,1417 +1,1205 @@
1
- ##
2
- # \namespace blurdev.gui.loggerwindow.loggerwindow
3
- #
4
- # \remarks LoggerWindow class is an overloaded python interpreter for blurdev
5
- #
6
- # \author beta@blur.com
7
- # \author Blur Studio
8
- # \date 01/15/08
9
- #
10
-
11
- from __future__ import print_function
12
- from __future__ import absolute_import
13
- import itertools
14
- import json
15
- import os
16
- import re
17
- import six
18
- import sys
19
- import time
20
- import warnings
21
- from datetime import datetime, timedelta
22
-
23
- import blurdev
24
- import blurdev.prefs
25
-
26
- from functools import partial
27
-
28
- from Qt.QtCore import Qt, QByteArray, QFileSystemWatcher, QFileInfo, QTimer, QSize
29
- from Qt import QtCore, QtWidgets
30
- from Qt.QtGui import QCursor, QFont, QFontDatabase, QIcon, QTextCursor
31
- from Qt.QtWidgets import (
32
- QApplication,
33
- QFileIconProvider,
34
- QInputDialog,
35
- QLabel,
36
- QMenu,
37
- QMessageBox,
38
- QSpinBox,
39
- QTextBrowser,
40
- QToolTip,
41
- QVBoxLayout,
42
- )
43
-
44
- from Qt import QtCompat
45
-
46
- from blurdev.logger import saveLoggerConfiguration
47
-
48
- from blurdev.gui import Window, Dialog
49
- from ..scintilla.delayable_engine import DelayableEngine
50
-
51
- from .workboxwidget import WorkboxWidget
52
-
53
- from .completer import CompleterMode
54
- from .level_buttons import LoggingLevelButton, DebugLevelButton
55
- from .set_text_editor_path_dialog import SetTextEditorPathDialog
56
-
57
- import __main__
58
-
59
-
60
- class LoggerWindow(Window):
61
- _instance = None
62
-
63
- def __init__(self, parent, runWorkbox=False):
64
- super(LoggerWindow, self).__init__(parent=parent)
65
- self.aboutToClearPathsEnabled = False
66
- self._stylesheet = 'Bright'
67
-
68
- # Create timer to autohide status messages
69
- self.statusTimer = QTimer()
70
- self.statusTimer.setSingleShot(True)
71
-
72
- # Store the previous time a font-resize wheel event was triggered to prevent
73
- # rapid-fire WheelEvents. Initialize to the current time.
74
- self.previousFontResizeTime = datetime.now()
75
-
76
- import blurdev.gui
77
-
78
- self.setWindowIcon(QIcon(blurdev.resourcePath('img/python_logger.png')))
79
- blurdev.gui.loadUi(__file__, self)
80
-
81
- self.uiConsoleTXT.pdbModeAction = self.uiPdbModeACT
82
- self.uiConsoleTXT.pdbUpdateVisibility = self.updatePdbVisibility
83
- self.updatePdbVisibility(False)
84
- self.uiConsoleTXT.reportExecutionTime = self.reportExecutionTime
85
- self.uiClearToLastPromptACT.triggered.connect(
86
- self.uiConsoleTXT.clearToLastPrompt
87
- )
88
- # If we don't disable this shortcut Qt won't respond to this classes or the
89
- # ConsoleEdit's
90
- self.uiConsoleTXT.uiClearToLastPromptACT.setShortcut('')
91
-
92
- # create the status reporting label
93
- self.uiStatusLBL = QLabel(self)
94
- self.uiMenuBar.setCornerWidget(self.uiStatusLBL)
95
-
96
- # create the workbox tabs
97
- self._currentTab = -1
98
- self._reloadRequested = set()
99
- # Setup delayable system
100
- self.delayable_engine = DelayableEngine.instance('logger', self)
101
- self.delayable_engine.set_delayable_enabled('smart_highlight', True)
102
- # Connect the tab widget signals
103
- self.uiWorkboxTAB.addTabClicked.connect(self.addWorkbox)
104
- self.uiWorkboxTAB.tabCloseRequested.connect(self.removeWorkbox)
105
- self.uiWorkboxTAB.currentChanged.connect(self.currentChanged)
106
- self.uiWorkboxTAB.tabBar().setContextMenuPolicy(Qt.CustomContextMenu)
107
- self.uiWorkboxTAB.tabBar().customContextMenuRequested.connect(
108
- self.workboxTabRightClick
109
- )
110
- # create the default workbox
111
- self.uiWorkboxWGT = self.addWorkbox(self.uiWorkboxTAB)
112
-
113
- # Create additional buttons in toolbar.
114
- self.uiDebugLevelBTN = DebugLevelButton(self)
115
- self.uiConsoleTOOLBAR.insertWidget(
116
- self.uiRunSelectedACT,
117
- self.uiDebugLevelBTN,
118
- )
119
- self.uiLoggingLevelBTN = LoggingLevelButton(self)
120
- self.uiConsoleTOOLBAR.insertWidget(
121
- self.uiRunSelectedACT,
122
- self.uiLoggingLevelBTN,
123
- )
124
- self.uiConsoleTOOLBAR.insertSeparator(self.uiRunSelectedACT)
125
-
126
- # create the pdb count widget
127
- self._pdbContinue = None
128
- self.uiPdbExecuteCountLBL = QLabel('x', self.uiPdbTOOLBAR)
129
- self.uiPdbExecuteCountLBL.setObjectName('uiPdbExecuteCountLBL')
130
- self.uiPdbTOOLBAR.addWidget(self.uiPdbExecuteCountLBL)
131
- self.uiPdbExecuteCountDDL = QSpinBox(self.uiPdbTOOLBAR)
132
- self.uiPdbExecuteCountDDL.setObjectName('uiPdbExecuteCountDDL')
133
- self.uiPdbExecuteCountDDL.setValue(1)
134
- self.uiPdbExecuteCountDDL.setRange(1, 10000)
135
- msg = (
136
- 'When the "next" and "step" buttons are pressed call them this many times.'
137
- )
138
- self.uiPdbExecuteCountDDL.setToolTip(msg)
139
- self.uiPdbTOOLBAR.addWidget(self.uiPdbExecuteCountDDL)
140
-
141
- # Initial configuration of the logToFile feature
142
- self._logToFilePath = None
143
- self._stds = None
144
- self.uiLogToFileClearACT.setVisible(False)
145
-
146
- self.uiCloseLoggerACT.triggered.connect(self.closeLogger)
147
-
148
- self.uiRunAllACT.triggered.connect(self.execAll)
149
- self.uiRunSelectedACT.triggered.connect(self.execSelected)
150
- self.uiPdbModeACT.triggered.connect(self.uiConsoleTXT.setPdbMode)
151
-
152
- self.uiPdbContinueACT.triggered.connect(self.uiConsoleTXT.pdbContinue)
153
- self.uiPdbStepACT.triggered.connect(partial(self.pdbRepeat, 'next'))
154
- self.uiPdbNextACT.triggered.connect(partial(self.pdbRepeat, 'step'))
155
- self.uiPdbUpACT.triggered.connect(self.uiConsoleTXT.pdbUp)
156
- self.uiPdbDownACT.triggered.connect(self.uiConsoleTXT.pdbDown)
157
-
158
- self.uiAutoCompleteEnabledACT.toggled.connect(self.setAutoCompleteEnabled)
159
-
160
- self.uiAutoCompleteCaseSensitiveACT.toggled.connect(self.setCaseSensitive)
161
-
162
- # Setup ability to cycle completer mode, and create action for each mode
163
- self.completerModeCycle = itertools.cycle(CompleterMode)
164
- # create CompleterMode submenu
165
- defaultMode = next(self.completerModeCycle)
166
- for mode in CompleterMode:
167
- modeName = mode.displayName()
168
- action = self.uiCompleterModeMENU.addAction(modeName)
169
- action.setObjectName('ui{}ModeACT'.format(modeName))
170
- action.setData(mode)
171
- action.setCheckable(True)
172
- action.setChecked(mode == defaultMode)
173
- completerMode = CompleterMode(mode)
174
- action.setToolTip(completerMode.toolTip())
175
- action.triggered.connect(partial(self.selectCompleterMode, action))
176
-
177
- self.uiCompleterModeMENU.addSeparator()
178
- action = self.uiCompleterModeMENU.addAction('Cycle mode')
179
- action.setObjectName('uiCycleModeACT')
180
- action.setShortcut(Qt.CTRL | Qt.Key_M)
181
- action.triggered.connect(self.cycleCompleterMode)
182
- self.uiCompleterModeMENU.hovered.connect(self.handleMenuHovered)
183
-
184
- # Workbox add/remove
185
- self.uiNewWorkboxACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.Key_N)
186
- self.uiNewWorkboxACT.triggered.connect(lambda: self.addWorkbox())
187
- self.uiCloseWorkboxACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.Key_W)
188
- self.uiCloseWorkboxACT.triggered.connect(
189
- lambda: self.removeWorkbox(self.uiWorkboxTAB.currentIndex())
190
- )
191
-
192
- # Browse previous commands
193
- self.uiGetPrevCmdACT.setShortcut(Qt.ALT | Qt.Key_Up)
194
- self.uiGetPrevCmdACT.triggered.connect(self.getPrevCommand)
195
- self.uiGetNextCmdACT.setShortcut(Qt.ALT | Qt.Key_Down)
196
- self.uiGetNextCmdACT.triggered.connect(self.getNextCommand)
197
-
198
- # Focus to console or to workbox, optionally copy seleciton or line
199
- self.uiFocusToConsoleACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.Key_PageUp)
200
- self.uiFocusToConsoleACT.triggered.connect(self.focusToConsole)
201
- self.uiCopyToConsoleACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.ALT | Qt.Key_PageUp)
202
- self.uiCopyToConsoleACT.triggered.connect(self.copyToConsole)
203
- self.uiFocusToWorkboxACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.Key_PageDown)
204
- self.uiFocusToWorkboxACT.triggered.connect(self.focusToWorkbox)
205
- self.uiCopyToWorkboxACT.setShortcut(
206
- Qt.CTRL | Qt.SHIFT | Qt.ALT | Qt.Key_PageDown
207
- )
208
- self.uiCopyToWorkboxACT.triggered.connect(self.copyToWorkbox)
209
-
210
- # Navigate workbox tabs
211
- self.uiNextTabACT.setShortcut(Qt.CTRL | Qt.Key_Tab)
212
- self.uiNextTabACT.triggered.connect(self.nextTab)
213
- self.uiPrevTabACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.Key_Tab)
214
- self.uiPrevTabACT.triggered.connect(self.prevTab)
215
-
216
- self.uiTab1ACT.triggered.connect(partial(self.gotoTabByIndex, 1))
217
- self.uiTab2ACT.triggered.connect(partial(self.gotoTabByIndex, 2))
218
- self.uiTab3ACT.triggered.connect(partial(self.gotoTabByIndex, 3))
219
- self.uiTab4ACT.triggered.connect(partial(self.gotoTabByIndex, 4))
220
- self.uiTab5ACT.triggered.connect(partial(self.gotoTabByIndex, 5))
221
- self.uiTab6ACT.triggered.connect(partial(self.gotoTabByIndex, 6))
222
- self.uiTab7ACT.triggered.connect(partial(self.gotoTabByIndex, 7))
223
- self.uiTab8ACT.triggered.connect(partial(self.gotoTabByIndex, 8))
224
- self.uiTabLastACT.triggered.connect(partial(self.gotoTabByIndex, -1))
225
-
226
- self.uiTab1ACT.setShortcut(Qt.CTRL | Qt.Key_1)
227
- self.uiTab2ACT.setShortcut(Qt.CTRL | Qt.Key_2)
228
- self.uiTab3ACT.setShortcut(Qt.CTRL | Qt.Key_3)
229
- self.uiTab4ACT.setShortcut(Qt.CTRL | Qt.Key_4)
230
- self.uiTab5ACT.setShortcut(Qt.CTRL | Qt.Key_5)
231
- self.uiTab6ACT.setShortcut(Qt.CTRL | Qt.Key_6)
232
- self.uiTab7ACT.setShortcut(Qt.CTRL | Qt.Key_7)
233
- self.uiTab8ACT.setShortcut(Qt.CTRL | Qt.Key_8)
234
- self.uiTabLastACT.setShortcut(Qt.CTRL | Qt.Key_9)
235
-
236
- self.uiCommentToggleACT.setShortcut(Qt.CTRL | Qt.Key_Slash)
237
- self.uiCommentToggleACT.triggered.connect(self.commentToggle)
238
-
239
- self.uiSpellCheckEnabledACT.toggled.connect(self.setSpellCheckEnabled)
240
- self.uiIndentationsTabsACT.toggled.connect(self.updateIndentationsUseTabs)
241
- self.uiCopyTabsToSpacesACT.toggled.connect(self.updateCopyIndentsAsSpaces)
242
- self.uiWordWrapACT.toggled.connect(self.setWordWrap)
243
- self.uiResetWarningFiltersACT.triggered.connect(warnings.resetwarnings)
244
- self.uiLogToFileACT.triggered.connect(self.installLogToFile)
245
- self.uiLogToFileClearACT.triggered.connect(self.clearLogToFile)
246
- self.uiClearLogACT.triggered.connect(self.clearLog)
247
- self.uiSaveConsoleSettingsACT.triggered.connect(
248
- lambda: self.recordPrefs(manual=True)
249
- )
250
- self.uiClearBeforeRunningACT.triggered.connect(self.setClearBeforeRunning)
251
- self.uiEditorVerticalACT.toggled.connect(self.adjustWorkboxOrientation)
252
- self.uiEnvironmentVarsACT.triggered.connect(self.showEnvironmentVars)
253
- self.uiBrowsePreferencesACT.triggered.connect(self.browsePreferences)
254
- self.uiAboutBlurdevACT.triggered.connect(self.showAbout)
255
- blurdev.core.aboutToClearPaths.connect(self.pathsAboutToBeCleared)
256
- self.uiSetFlashWindowIntervalACT.triggered.connect(self.setFlashWindowInterval)
257
-
258
- self.uiSetPreferredTextEditorPathACT.triggered.connect(
259
- self.openSetPreferredTextEditorDialog
260
- )
261
-
262
- # Tooltips - Qt4 doesn't have a ToolTipsVisible method, so we fake it
263
- regEx = ".*"
264
- menus = self.findChildren(QtWidgets.QMenu, QtCore.QRegExp(regEx))
265
- for menu in menus:
266
- menu.hovered.connect(self.handleMenuHovered)
267
-
268
- self.uiClearLogACT.setIcon(QIcon(blurdev.resourcePath('img/logger/clear.png')))
269
- self.uiSaveConsoleSettingsACT.setIcon(
270
- QIcon(blurdev.resourcePath('img/logger/save.png'))
271
- )
272
- self.uiAboutBlurdevACT.setIcon(
273
- QIcon(blurdev.resourcePath('img/logger/about.png'))
274
- )
275
- self.uiCloseLoggerACT.setIcon(
276
- QIcon(blurdev.resourcePath('img/logger/close.png'))
277
- )
278
-
279
- self.uiPdbContinueACT.setIcon(
280
- QIcon(blurdev.resourcePath('img/logger/play.png'))
281
- )
282
- self.uiPdbStepACT.setIcon(
283
- QIcon(blurdev.resourcePath('img/logger/arrow_forward.png'))
284
- )
285
- self.uiPdbNextACT.setIcon(
286
- QIcon(blurdev.resourcePath('img/logger/subdirectory_arrow_right.png'))
287
- )
288
- self.uiPdbUpACT.setIcon(QIcon(blurdev.resourcePath('img/logger/up.png')))
289
- self.uiPdbDownACT.setIcon(QIcon(blurdev.resourcePath('img/logger/down.png')))
290
-
291
- # Setting toolbar icon size.
292
-
293
- self.uiConsoleTOOLBAR.setIconSize(QSize(18, 18))
294
-
295
- # Start the filesystem monitor
296
- self._openFileMonitor = QFileSystemWatcher(self)
297
- self._openFileMonitor.fileChanged.connect(self.linkedFileChanged)
298
-
299
- # Make action shortcuts available anywhere in the Logger
300
- self.addAction(self.uiClearLogACT)
301
-
302
- # calling setLanguage resets this value to False
303
- self.restorePrefs()
304
-
305
- # add font menu list
306
- curFamily = self.console().font().family()
307
- fontDB = QFontDatabase()
308
- fontFamilies = fontDB.families(QFontDatabase.Latin)
309
- monospaceFonts = [fam for fam in fontFamilies if fontDB.isFixedPitch(fam)]
310
-
311
- self.uiMonospaceFontMENU.clear()
312
- self.uiProportionalFontMENU.clear()
313
-
314
- for family in fontFamilies:
315
- if family in monospaceFonts:
316
- action = self.uiMonospaceFontMENU.addAction(family)
317
- else:
318
- action = self.uiProportionalFontMENU.addAction(family)
319
- action.setObjectName(u'ui{}FontACT'.format(family))
320
- action.setCheckable(True)
321
- action.setChecked(family == curFamily)
322
- action.triggered.connect(partial(self.selectFont, action))
323
-
324
- # add stylesheet menu options.
325
- for style_name in blurdev.core.styleSheets('logger'):
326
- action = self.uiStyleMENU.addAction(style_name)
327
- action.setObjectName('ui{}ACT'.format(style_name))
328
- action.setCheckable(True)
329
- action.setChecked(self._stylesheet == style_name)
330
- action.triggered.connect(partial(self.setStyleSheet, style_name))
331
-
332
- self.uiConsoleTOOLBAR.show()
333
- loggerName = QApplication.instance().translate(
334
- 'PythonLoggerWindow', 'Python Logger'
335
- )
336
- self.setWindowTitle(
337
- '%s - %s - %s %i-bit'
338
- % (
339
- loggerName,
340
- blurdev.core.objectName().capitalize(),
341
- '%i.%i.%i' % sys.version_info[:3],
342
- blurdev.osystem.getPointerSize(),
343
- )
344
- )
345
-
346
- self.setupRunWorkbox()
347
-
348
- # Run the current workbox after the LoggerWindow is shown.
349
- if runWorkbox:
350
- # By using two singleShot timers, we can show and draw the LoggerWindow,
351
- # then call execAll. This makes it easier to see what code you are running
352
- # before it has finished running completely.
353
- # QTimer.singleShot(0, lambda: QTimer.singleShot(0, self.execAll))
354
- QTimer.singleShot(
355
- 0, lambda: QTimer.singleShot(0, lambda: self.runWorkbox(runWorkbox))
356
- )
357
-
358
- def commentToggle(self):
359
- self.uiWorkboxTAB.currentWidget().commentToggle()
360
-
361
- def runWorkbox(self, indicator):
362
- """This is a function which will be added to __main__, and therefore available
363
- to PythonLogger users. It will accept a python-like index (positive or
364
- negative), a string representing the name of the chosen Workbox, or a boolean
365
- which support existing command line launching functionality which will auto-run
366
- the last workbox up launch.
367
-
368
- Args:
369
- indicator(int, str, boolean): Used to define which workbox to run.
370
-
371
- Raises:
372
- Exception: "Cannot call current workbox."
373
-
374
- Example Usages:
375
- runWorkbox(3)
376
- runWorkbox(-2)
377
- runWorkbox('test')
378
- runWorkbox('stuff.py')
379
-
380
- (from command line): blurdev launch Python_Logger --run_workbox
381
-
382
- """
383
- pyLogger = blurdev.core.logger()
384
- workboxTab = pyLogger.uiWorkboxTAB
385
- workboxCount = workboxTab.count()
386
-
387
- # Determine workbox index
388
- index = None
389
-
390
- # If indicator is True, run last workbox
391
- if isinstance(indicator, bool):
392
- if indicator:
393
- index = workboxCount - 1
394
- # If indicator is an int, use as normal python index
395
- elif isinstance(indicator, int):
396
- if indicator < 0:
397
- num = workboxCount + indicator
398
- else:
399
- num = indicator
400
- if num >= 0 and num < workboxCount:
401
- index = num
402
- # If indicator is a string, find first tab with that name
403
- elif isinstance(indicator, six.string_types):
404
- for i in range(workboxCount):
405
- if workboxTab.tabText(i) == indicator:
406
- index = i
407
- break
408
- if index is not None:
409
- workbox = workboxTab.widget(index)
410
- if workbox.hasFocus():
411
- raise Exception("Cannot call current workbox.")
412
- else:
413
- workbox.execAll()
414
-
415
- def setupRunWorkbox(self):
416
- """We will bind the runWordbox function on __main__, which makes is available to
417
- code running within PythonLogger.
418
- """
419
- __main__.runWorkbox = self.runWorkbox
420
-
421
- def openSetPreferredTextEditorDialog(self):
422
- dlg = SetTextEditorPathDialog(parent=self)
423
- dlg.exec_()
424
-
425
- def focusToConsole(self):
426
- """Move focus to the console"""
427
- self.console().setFocus()
428
-
429
- def focusToWorkbox(self):
430
- """Move focus to the current workbox"""
431
- self.uiWorkboxTAB.currentWidget().setFocus()
432
-
433
- def copyToConsole(self):
434
- """Copy current selection or line from workbox to console"""
435
- workbox = self.uiWorkboxTAB.currentWidget()
436
- if not workbox.hasFocus():
437
- return
438
-
439
- text = workbox.selectedText()
440
- if not text:
441
- line, index = workbox.getCursorPosition()
442
- text = workbox.text(line)
443
- text = text.rstrip('\r\n')
444
- if not text:
445
- return
446
-
447
- cursor = self.console().textCursor()
448
- if cursor.hasSelection():
449
- cursor.removeSelectedText()
450
-
451
- self.console().insertPlainText(text)
452
- self.focusToConsole()
453
-
454
- def copyToWorkbox(self):
455
- """Copy current selection or line from console to workbox"""
456
- console = self.console()
457
- if not console.hasFocus():
458
- return
459
-
460
- cursor = console.textCursor()
461
- if not cursor.hasSelection():
462
- cursor.select(QTextCursor.LineUnderCursor)
463
- text = cursor.selectedText()
464
- prompt = console.prompt()
465
- if text.startswith(prompt):
466
- text = text[len(prompt) :]
467
- text = text.lstrip()
468
-
469
- outputPrompt = console.outputPrompt()
470
- outputPrompt = outputPrompt.rstrip()
471
- if text.startswith(outputPrompt):
472
- text = text[len(outputPrompt) :]
473
- text = text.lstrip()
474
-
475
- if not text:
476
- return
477
-
478
- workbox = self.uiWorkboxTAB.currentWidget()
479
- workbox.removeSelectedText()
480
- workbox.insert(text)
481
-
482
- line, index = workbox.getCursorPosition()
483
- index += len(text)
484
- workbox.setCursorPosition(line, index)
485
-
486
- self.focusToWorkbox()
487
-
488
- def getNextCommand(self):
489
- if hasattr(self.console(), 'getNextCommand'):
490
- self.console().getNextCommand()
491
-
492
- def getPrevCommand(self):
493
- if hasattr(self.console(), 'getPrevCommand'):
494
- self.console().getPrevCommand()
495
-
496
- def wheelEvent(self, event):
497
- """adjust font size on ctrl+scrollWheel"""
498
- if event.modifiers() == Qt.ControlModifier:
499
- # WheelEvents can be emitted in a cluster, but we only want one at a time
500
- # (ie to change font size by 1, rather than 2 or 3). Let's bail if previous
501
- # font-resize wheel event was within a certain threshhold.
502
- now = datetime.now()
503
- elapsed = now - self.previousFontResizeTime
504
- tolerance = timedelta(microseconds=100000)
505
- if elapsed < tolerance:
506
- return
507
- self.previousFontResizeTime = now
508
-
509
- # QT4 presents QWheelEvent.delta(), QT5 has QWheelEvent.angleDelta().y()
510
- if hasattr(event, 'delta'): # Qt4
511
- delta = event.delta()
512
- else: # QT5
513
- delta = event.angleDelta().y()
514
-
515
- # convert delta to +1 or -1, depending
516
- delta = delta / abs(delta)
517
- minSize = 5
518
- maxSize = 50
519
- font = self.console().font()
520
- newSize = font.pointSize() + delta
521
- newSize = max(min(newSize, maxSize), minSize)
522
-
523
- font.setPointSize(newSize)
524
- self.console().setConsoleFont(font)
525
-
526
- for index in range(self.uiWorkboxTAB.count()):
527
- workbox = self.uiWorkboxTAB.widget(index)
528
-
529
- marginsFont = workbox.marginsFont()
530
- marginsFont.setPointSize(newSize)
531
- workbox.setMarginsFont(marginsFont)
532
-
533
- workbox.setWorkboxFont(font)
534
- else:
535
- Window.wheelEvent(self, event)
536
-
537
- def handleMenuHovered(self, action):
538
- """Qt4 doesn't have a ToolTipsVisible method, so we fake it"""
539
- # Don't show if it's just the text of the action
540
- text = re.sub(r"(?<!&)&(?!&)", "", action.text())
541
- text = text.replace('...', '')
542
-
543
- if text == action.toolTip():
544
- text = ''
545
- else:
546
- text = action.toolTip()
547
-
548
- menu = action.parentWidget()
549
- QToolTip.showText(QCursor.pos(), text, menu)
550
-
551
- def findCurrentFontAction(self):
552
- """Find and return current font's action"""
553
- actions = self.uiMonospaceFontMENU.actions()
554
- actions.extend(self.uiProportionalFontMENU.actions())
555
-
556
- action = None
557
- for act in actions:
558
- if act.isChecked():
559
- action = act
560
- break
561
-
562
- return action
563
-
564
- def selectFont(self, action):
565
- """
566
- Set console and workbox font to current font
567
- Args:
568
- action: menu action associated with chosen font
569
- """
570
-
571
- actions = self.uiMonospaceFontMENU.actions()
572
- actions.extend(self.uiProportionalFontMENU.actions())
573
-
574
- for act in actions:
575
- act.setChecked(act == action)
576
-
577
- family = action.text()
578
- font = self.console().font()
579
- font.setFamily(family)
580
- self.console().setConsoleFont(font)
581
-
582
- for index in range(self.uiWorkboxTAB.count()):
583
- workbox = self.uiWorkboxTAB.widget(index)
584
-
585
- workbox.documentFont = font
586
- workbox.setMarginsFont(font)
587
- workbox.setWorkboxFont(font)
588
-
589
- @classmethod
590
- def _genPrefName(cls, baseName, index):
591
- if index:
592
- baseName = '{name}{index}'.format(name=baseName, index=index)
593
- return baseName
594
-
595
- def addWorkbox(self, tabWidget=None, title='Workbox', closable=True):
596
- if tabWidget is None:
597
- tabWidget = self.uiWorkboxTAB
598
- workbox = WorkboxWidget(tabWidget, delayable_engine=self.delayable_engine.name)
599
- workbox.setConsole(self.uiConsoleTXT)
600
- workbox.setMinimumHeight(1)
601
- index = tabWidget.addTab(workbox, title)
602
- workbox.setLanguage('Python')
603
-
604
- # update the lexer
605
- fontAction = self.findCurrentFontAction()
606
- if fontAction is not None:
607
- self.selectFont(fontAction)
608
- else:
609
- workbox.setMarginsFont(workbox.font())
610
-
611
- if closable:
612
- # If only one tab is visible, don't show the close tab button
613
- tabWidget.setTabsClosable(tabWidget.count() != 1)
614
- tabWidget.setCurrentIndex(index)
615
- workbox.setIndentationsUseTabs(self.uiIndentationsTabsACT.isChecked())
616
- workbox.copyIndentsAsSpaces = self.uiCopyTabsToSpacesACT.isChecked()
617
-
618
- workbox.setFocus()
619
- if self.uiLinesInNewWorkboxACT.isChecked():
620
- workbox.setText("\n" * 19)
621
-
622
- return workbox
623
-
624
- def adjustWorkboxOrientation(self, state):
625
- if state:
626
- self.uiSplitterSPLIT.setOrientation(Qt.Horizontal)
627
- else:
628
- self.uiSplitterSPLIT.setOrientation(Qt.Vertical)
629
-
630
- def browsePreferences(self):
631
- path = blurdev.prefs.prefs_path()
632
- blurdev.osystem.explore(path)
633
-
634
- def console(self):
635
- return self.uiConsoleTXT
636
-
637
- def clearLog(self):
638
- self.uiConsoleTXT.clear()
639
-
640
- def clearLogToFile(self):
641
- """If installLogToFile has been called, clear the stdout."""
642
- if self._stds:
643
- self._stds[0].clear(stamp=True)
644
-
645
- def closeEvent(self, event):
646
- self.recordPrefs()
647
- saveLoggerConfiguration()
648
-
649
- Window.closeEvent(self, event)
650
- if self.uiConsoleTOOLBAR.isFloating():
651
- self.uiConsoleTOOLBAR.hide()
652
-
653
- def closeLogger(self):
654
- self.close()
655
-
656
- def createShortcut(self, function):
657
- msg = (
658
- 'Do you want to create a public shortcut? If not it will be '
659
- 'created on your user desktop.'
660
- )
661
- result = QMessageBox.question(
662
- self,
663
- 'Create On Public?',
664
- msg,
665
- QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
666
- )
667
- if result != QMessageBox.Cancel:
668
- public = result == QMessageBox.Yes
669
- function(common=int(public))
670
-
671
- def execAll(self):
672
- """Clears the console before executing all workbox code"""
673
- if self.uiClearBeforeRunningACT.isChecked():
674
- self.clearLog()
675
- self.uiWorkboxTAB.currentWidget().execAll()
676
-
677
- if self.uiAutoPromptACT.isChecked():
678
- console = self.console()
679
- prompt = console.prompt()
680
- console.startPrompt(prompt)
681
-
682
- def execSelected(self):
683
- """Clears the console before executing selected workbox code"""
684
- if self.uiClearBeforeRunningACT.isChecked():
685
- self.clearLog()
686
- self.uiWorkboxTAB.currentWidget().execSelected()
687
-
688
- def keyPressEvent(self, event):
689
- # Fix 'Maya : Qt tools lose focus' https://redmine.blur.com/issues/34430
690
- if event.modifiers() & (Qt.AltModifier | Qt.ControlModifier | Qt.ShiftModifier):
691
- pass
692
- else:
693
- super(LoggerWindow, self).keyPressEvent(event)
694
-
695
- def pdbRepeat(self, commandText):
696
- # If we need to repeat the command store that info
697
- value = self.uiPdbExecuteCountDDL.value()
698
- if value > 1:
699
- # The first request is triggered at the end of this function, so we need to
700
- # store one less than requested
701
- self._pdbContinue = (value - 1, commandText)
702
- else:
703
- self._pdbContinue = None
704
- # Send the first command
705
- self.uiConsoleTXT.pdbSendCommand(commandText)
706
-
707
- def pathsAboutToBeCleared(self):
708
- if self.uiClearLogOnRefreshACT.isChecked():
709
- self.clearLog()
710
-
711
- def reportExecutionTime(self, seconds):
712
- """Update status text with seconds passed in."""
713
- self.setStatusText('Exec: {:0.04f} Seconds'.format(seconds))
714
-
715
- def recordPrefs(self, manual=False):
716
- if not manual and not self.uiAutoSaveSettingssACT.isChecked():
717
- return
718
-
719
- _prefs = self.load_prefs()
720
- geo = self.geometry()
721
- _prefs.update(
722
- {
723
- 'loggergeom': [geo.x(), geo.y(), geo.width(), geo.height()],
724
- 'windowState': int(self.windowState()),
725
- 'SplitterVertical': self.uiEditorVerticalACT.isChecked(),
726
- 'SplitterSize': self.uiSplitterSPLIT.sizes(),
727
- 'tabIndent': self.uiIndentationsTabsACT.isChecked(),
728
- 'copyIndentsAsSpaces': self.uiCopyTabsToSpacesACT.isChecked(),
729
- 'hintingEnabled': self.uiAutoCompleteEnabledACT.isChecked(),
730
- 'spellCheckEnabled': self.uiSpellCheckEnabledACT.isChecked(),
731
- 'wordWrap': self.uiWordWrapACT.isChecked(),
732
- 'clearBeforeRunning': self.uiClearBeforeRunningACT.isChecked(),
733
- 'clearBeforeEnvRefresh': self.uiClearLogOnRefreshACT.isChecked(),
734
- 'toolbarStates': str(self.saveState().toHex(), 'utf-8'),
735
- 'consoleFont': self.console().font().toString(),
736
- 'uiAutoSaveSettingssACT': self.uiAutoSaveSettingssACT.isChecked(),
737
- 'uiAutoPromptACT': self.uiAutoPromptACT.isChecked(),
738
- 'uiLinesInNewWorkboxACT': self.uiLinesInNewWorkboxACT.isChecked(),
739
- 'uiErrorHyperlinksACT': self.uiErrorHyperlinksACT.isChecked(),
740
- 'textEditorPath': self.textEditorPath,
741
- 'textEditorCmdTempl': self.textEditorCmdTempl,
742
- 'currentStyleSheet': self._stylesheet,
743
- 'flashTime': self.uiConsoleTXT.flashTime,
744
- }
745
- )
746
-
747
- # completer settings
748
- completer = self.console().completer()
749
- _prefs["caseSensitive"] = completer.caseSensitive()
750
- _prefs["completerMode"] = completer.completerMode().value
751
-
752
- for index in range(self.uiWorkboxTAB.count()):
753
- workbox = self.uiWorkboxTAB.widget(index)
754
- _prefs[self._genPrefName('WorkboxText', index)] = workbox.text()
755
- lexer = workbox.lexer()
756
- if lexer:
757
- font = lexer.font(0)
758
- else:
759
- font = workbox.font()
760
- _prefs[self._genPrefName('workboxFont', index)] = font.toString()
761
- _prefs[
762
- self._genPrefName('workboxMarginFont', index)
763
- ] = workbox.marginsFont().toString()
764
- _prefs[
765
- self._genPrefName('workboxTabTitle', index)
766
- ] = self.uiWorkboxTAB.tabBar().tabText(index)
767
-
768
- linkPath = ''
769
- if workbox._fileMonitoringActive:
770
- linkPath = workbox.filename()
771
- if os.path.isfile(linkPath):
772
- workbox.save()
773
- else:
774
- self.unlinkTab(index)
775
-
776
- _prefs[self._genPrefName('workboxPath', index)] = linkPath
777
-
778
- _prefs['WorkboxCount'] = self.uiWorkboxTAB.count()
779
- _prefs['WorkboxCurrentIndex'] = self.uiWorkboxTAB.currentIndex()
780
-
781
- if self._stylesheet == 'Custom':
782
- _prefs['styleSheet'] = self.styleSheet()
783
-
784
- self.save_prefs(_prefs)
785
-
786
- def load_prefs(self):
787
- prefs_path = blurdev.prefs.prefs_path('logger_pref.json')
788
- if os.path.exists(prefs_path):
789
- with open(prefs_path) as fp:
790
- return json.load(fp)
791
- return {}
792
-
793
- def save_prefs(self, _prefs):
794
- # Save preferences to disk
795
- prefs_path = blurdev.prefs.prefs_path('logger_pref.json')
796
- dirname = os.path.dirname(prefs_path)
797
- if not os.path.exists(dirname):
798
- os.makedirs(dirname)
799
- with open(prefs_path, 'w') as fp:
800
- json.dump(_prefs, fp, indent=4)
801
-
802
- def restorePrefs(self):
803
- prefs = self.load_prefs()
804
-
805
- if 'loggergeom' in prefs:
806
- self.setGeometry(*prefs['loggergeom'])
807
- self.uiEditorVerticalACT.setChecked(prefs.get('SplitterVertical', False))
808
- self.adjustWorkboxOrientation(self.uiEditorVerticalACT.isChecked())
809
-
810
- sizes = prefs.get('SplitterSize')
811
- if sizes:
812
- self.uiSplitterSPLIT.setSizes(sizes)
813
- self.setWindowState(Qt.WindowStates(prefs.get('windowState', 0)))
814
- self.uiIndentationsTabsACT.setChecked(prefs.get('tabIndent', True))
815
- self.uiCopyTabsToSpacesACT.setChecked(prefs.get('copyIndentsAsSpaces', False))
816
- self.uiAutoCompleteEnabledACT.setChecked(prefs.get('hintingEnabled', True))
817
-
818
- # completer settings
819
- self.setCaseSensitive(prefs.get('caseSensitive', True))
820
- completerMode = CompleterMode(prefs.get('completerMode', 0))
821
- self.cycleToCompleterMode(completerMode)
822
- self.setCompleterMode(completerMode)
823
-
824
- self.setSpellCheckEnabled(self.uiSpellCheckEnabledACT.isChecked())
825
- self.uiSpellCheckEnabledACT.setChecked(prefs.get('spellCheckEnabled', False))
826
- self.uiConsoleTXT.completer().setEnabled(
827
- self.uiAutoCompleteEnabledACT.isChecked()
828
- )
829
- self.uiAutoSaveSettingssACT.setChecked(
830
- prefs.get('uiAutoSaveSettingssACT', True)
831
- )
832
-
833
- self.uiAutoPromptACT.setChecked(prefs.get('uiAutoPromptACT', False))
834
- self.uiLinesInNewWorkboxACT.setChecked(
835
- prefs.get('uiLinesInNewWorkboxACT', False)
836
- )
837
- self.uiErrorHyperlinksACT.setChecked(prefs.get('uiErrorHyperlinksACT', True))
838
-
839
- # External text editor filepath and command template
840
- defaultExePath = r"C:\Program Files\Sublime Text 3\sublime_text.exe"
841
- defaultCmd = r"{exePath} {modulePath}:{lineNum}"
842
- self.textEditorPath = prefs.get('textEditorPath', defaultExePath)
843
- self.textEditorCmdTempl = prefs.get('textEditorCmdTempl', defaultCmd)
844
-
845
- self.uiWordWrapACT.setChecked(prefs.get('wordWrap', True))
846
- self.setWordWrap(self.uiWordWrapACT.isChecked())
847
- self.uiClearBeforeRunningACT.setChecked(prefs.get('clearBeforeRunning', False))
848
- self.uiClearLogOnRefreshACT.setChecked(
849
- prefs.get('clearBeforeEnvRefresh', False)
850
- )
851
- self.setClearBeforeRunning(self.uiClearBeforeRunningACT.isChecked())
852
-
853
- _font = prefs.get('consoleFont', None)
854
- if _font:
855
- font = QFont()
856
- if font.fromString(_font):
857
- self.console().setConsoleFont(font)
858
-
859
- # Restore the workboxes
860
- count = prefs.get('WorkboxCount', 1)
861
- for _ in range(count - self.uiWorkboxTAB.count()):
862
- # create each of the workbox tabs
863
- self.addWorkbox(self.uiWorkboxTAB)
864
- for index in range(count):
865
- workbox = self.uiWorkboxTAB.widget(index)
866
- workbox.setText(prefs.get(self._genPrefName('WorkboxText', index), ''))
867
-
868
- workboxPath = prefs.get(self._genPrefName('workboxPath', index), '')
869
- if os.path.isfile(workboxPath):
870
- self.linkTab(index, workboxPath)
871
-
872
- _font = prefs.get(self._genPrefName('workboxFont', index), None)
873
- if _font:
874
- font = QFont()
875
- if font.fromString(_font):
876
- lexer = workbox.lexer()
877
- if lexer:
878
- font = lexer.setFont(font)
879
- else:
880
- font = workbox.setFont(font)
881
- _font = prefs.get(self._genPrefName('workboxMarginFont', index), None)
882
- if _font:
883
- font = QFont()
884
- if font.fromString(_font):
885
- workbox.setMarginsFont(font)
886
- tabText = prefs.get(self._genPrefName('workboxTabTitle', index), 'Workbox')
887
- self.uiWorkboxTAB.tabBar().setTabText(index, tabText)
888
-
889
- self.uiWorkboxTAB.setCurrentIndex(prefs.get('WorkboxCurrentIndex', 0))
890
-
891
- self._stylesheet = prefs.get('currentStyleSheet', 'Bright')
892
- if self._stylesheet == 'Custom':
893
- self.setStyleSheet(prefs.get('styleSheet', ''))
894
- else:
895
- self.setStyleSheet(self._stylesheet)
896
- self.uiConsoleTXT.flashTime = prefs.get('flashTime', 1.0)
897
-
898
- self.restoreToolbars(prefs=prefs)
899
-
900
- def restoreToolbars(self, prefs=None):
901
- if prefs is None:
902
- prefs = self.load_prefs()
903
-
904
- state = prefs.get('toolbarStates', None)
905
- if state:
906
- state = QByteArray.fromHex(bytes(state, 'utf-8'))
907
- self.restoreState(state)
908
- # Ensure uiPdbTOOLBAR respects the current pdb mode
909
- self.uiConsoleTXT.setPdbMode(self.uiConsoleTXT.pdbMode())
910
-
911
- def removeWorkbox(self, index):
912
- if self.uiWorkboxTAB.count() == 1:
913
- msg = "You have to leave at least one tab open."
914
- QMessageBox.critical(self, 'Tab can not be closed.', msg, QMessageBox.Ok)
915
- return
916
- msg = (
917
- "Would you like to donate this tabs contents to the "
918
- "/dev/null fund for wayward code?"
919
- )
920
- if (
921
- QMessageBox.question(
922
- self, 'Donate to the cause?', msg, QMessageBox.Yes | QMessageBox.Cancel
923
- )
924
- == QMessageBox.Yes
925
- ):
926
- self.uiWorkboxTAB.removeTab(index)
927
- self.uiWorkboxTAB.setTabsClosable(self.uiWorkboxTAB.count() != 1)
928
-
929
- def setAutoCompleteEnabled(self, state):
930
- self.uiConsoleTXT.completer().setEnabled(state)
931
- for index in range(self.uiWorkboxTAB.count()):
932
- tab = self.uiWorkboxTAB.widget(index)
933
- if state:
934
- tab.setAutoCompletionSource(tab.AcsAll)
935
- else:
936
- tab.setAutoCompletionSource(tab.AcsNone)
937
-
938
- def setSpellCheckEnabled(self, state):
939
- try:
940
- self.delayable_engine.set_delayable_enabled('spell_check', state)
941
- except KeyError:
942
- # Spell check can not be enabled
943
- if self.isVisible():
944
- # Only show warning if Logger is visible and also disable the action
945
- self.uiSpellCheckEnabledACT.setDisabled(True)
946
- QMessageBox.warning(
947
- self, "Spell-Check", 'Unable to activate spell check.'
948
- )
949
-
950
- def setStatusText(self, txt):
951
- """Set the text shown in the menu corner of the menu bar.
952
-
953
- Args:
954
- txt (str): The text to show in the status text label.
955
- """
956
- self.uiStatusLBL.setText(txt)
957
- self.uiMenuBar.adjustSize()
958
-
959
- def clearStatusText(self):
960
- """Clear any displayed status text"""
961
- self.uiStatusLBL.setText('')
962
- self.uiMenuBar.adjustSize()
963
-
964
- def autoHideStatusText(self):
965
- """Set timer to automatically clear status text"""
966
- if self.statusTimer.isActive():
967
- self.statusTimer.stop()
968
- self.statusTimer.singleShot(2000, self.clearStatusText)
969
- self.statusTimer.start()
970
-
971
- def setStyleSheet(self, stylesheet, recordPrefs=True):
972
- """Accepts the name of a stylesheet included with blurdev, or a full
973
- path to any stylesheet. If given None, it will default to Bright.
974
- """
975
- sheet = None
976
- if stylesheet is None:
977
- stylesheet = 'Bright'
978
- if os.path.isfile(stylesheet):
979
- # A path to a stylesheet was passed in
980
- with open(stylesheet) as f:
981
- sheet = f.read()
982
- self._stylesheet = stylesheet
983
- else:
984
- # Try to find an installed stylesheet with the given name
985
- sheet, valid = blurdev.core.readStyleSheet('logger/{}'.format(stylesheet))
986
- if valid:
987
- self._stylesheet = stylesheet
988
- else:
989
- # Assume the user passed the text of the stylesheet directly
990
- sheet = stylesheet
991
- self._stylesheet = 'Custom'
992
-
993
- # Load the stylesheet
994
- if sheet is not None:
995
- super(LoggerWindow, self).setStyleSheet(sheet)
996
-
997
- # Update the style menu
998
- for act in self.uiStyleMENU.actions():
999
- name = act.objectName()
1000
- isCurrent = name == 'ui{}ACT'.format(self._stylesheet)
1001
- act.setChecked(isCurrent)
1002
-
1003
- # Notify widgets that the styleSheet has changed
1004
- blurdev.core.styleSheetChanged.emit(blurdev.core.styleSheet())
1005
-
1006
- def setCaseSensitive(self, state):
1007
- """Set completer case-sensivity"""
1008
- completer = self.console().completer()
1009
- completer.setCaseSensitive(state)
1010
- self.uiAutoCompleteCaseSensitiveACT.setChecked(state)
1011
- self.reportCaseChange(state)
1012
- completer.refreshList()
1013
-
1014
- def toggleCaseSensitive(self):
1015
- """Toggle completer case-sensitivity"""
1016
- state = self.console().completer().caseSensitive()
1017
- self.reportCaseChange(state)
1018
- self.setCaseSensitive(not state)
1019
-
1020
- # Completer Modes
1021
- def cycleCompleterMode(self):
1022
- """Cycle comleter mode"""
1023
- completerMode = next(self.completerModeCycle)
1024
- self.setCompleterMode(completerMode)
1025
- self.reportCompleterModeChange(completerMode)
1026
-
1027
- def cycleToCompleterMode(self, completerMode):
1028
- """
1029
- Syncs the completerModeCycle iterator to currently chosen completerMode
1030
- Args:
1031
- completerMode: Chosen CompleterMode ENUM member
1032
- """
1033
- for _ in range(len(CompleterMode)):
1034
- tempMode = next(self.completerModeCycle)
1035
- if tempMode == completerMode:
1036
- break
1037
-
1038
- def setCompleterMode(self, completerMode):
1039
- """
1040
- Set the completer mode to chosen mode
1041
- Args:
1042
- completerMode: Chosen CompleterMode ENUM member
1043
- """
1044
- completer = self.console().completer()
1045
-
1046
- completer.setCompleterMode(completerMode)
1047
- completer.buildCompleter()
1048
-
1049
- for action in self.uiCompleterModeMENU.actions():
1050
- action.setChecked(action.data() == completerMode)
1051
-
1052
- def selectCompleterMode(self, action):
1053
- if not action.isChecked():
1054
- action.setChecked(True)
1055
- return
1056
- """
1057
- Handle when completer mode is chosen via menu
1058
- Will sync mode iterator and set the completion mode
1059
- Args:
1060
- action: the menu action associated with the chosen mode
1061
- """
1062
-
1063
- # update cycleToCompleterMode to current Mode
1064
- mode = action.data()
1065
- self.cycleToCompleterMode(mode)
1066
- self.setCompleterMode(mode)
1067
-
1068
- def reportCaseChange(self, state):
1069
- """Update status text with current Case Sensitivity Mode"""
1070
- text = "Case Sensitive " if state else "Case Insensitive "
1071
- self.setStatusText(text)
1072
- self.autoHideStatusText()
1073
-
1074
- def reportCompleterModeChange(self, mode):
1075
- """Update status text with current Completer Mode"""
1076
- self.setStatusText('Completer Mode: {} '.format(mode.displayName()))
1077
- self.autoHideStatusText()
1078
-
1079
- def setClearBeforeRunning(self, state):
1080
- self.uiRunSelectedACT.setIcon(
1081
- QIcon(blurdev.resourcePath('img/logger/playlist_play.png'))
1082
- )
1083
- self.uiRunAllACT.setIcon(QIcon(blurdev.resourcePath('img/logger/play.png')))
1084
-
1085
- def setFlashWindowInterval(self):
1086
- value = self.uiConsoleTXT.flashTime
1087
- msg = (
1088
- 'If running code in the logger takes X seconds or longer,\n'
1089
- 'the window will flash if it is not in focus.\n'
1090
- 'Setting the value to zero will disable flashing.'
1091
- )
1092
- value, success = QInputDialog.getDouble(self, 'Set flash window', msg, value)
1093
- if success:
1094
- self.uiConsoleTXT.flashTime = value
1095
-
1096
- def setWordWrap(self, state):
1097
- if state:
1098
- self.uiConsoleTXT.setLineWrapMode(self.uiConsoleTXT.WidgetWidth)
1099
- else:
1100
- self.uiConsoleTXT.setLineWrapMode(self.uiConsoleTXT.NoWrap)
1101
-
1102
- def showAbout(self):
1103
- msg = blurdev.core.aboutBlurdev()
1104
- QMessageBox.information(self, 'About blurdev', msg)
1105
-
1106
- def showEnvironmentVars(self):
1107
- dlg = Dialog(blurdev.core.logger())
1108
- lyt = QVBoxLayout(dlg)
1109
- lbl = QTextBrowser(dlg)
1110
- lyt.addWidget(lbl)
1111
- dlg.setWindowTitle('Blurdev Environment Variable Help')
1112
- with open(blurdev.resourcePath('environment_variables.html')) as f:
1113
- lbl.setText(f.read().replace('\n', ''))
1114
- dlg.setMinimumSize(600, 400)
1115
- dlg.show()
1116
-
1117
- def showEvent(self, event):
1118
- super(LoggerWindow, self).showEvent(event)
1119
- self.restoreToolbars()
1120
- self.updateIndentationsUseTabs()
1121
- self.updateCopyIndentsAsSpaces()
1122
-
1123
- # Adjust the minimum height of the label so it's text is the same as
1124
- # the action menu text
1125
- height = self.uiMenuBar.actionGeometry(self.uiFileMENU.menuAction()).height()
1126
- self.uiStatusLBL.setMinimumHeight(height)
1127
-
1128
- def updateCopyIndentsAsSpaces(self):
1129
- for index in range(self.uiWorkboxTAB.count()):
1130
- tab = self.uiWorkboxTAB.widget(index)
1131
- tab.copyIndentsAsSpaces = self.uiCopyTabsToSpacesACT.isChecked()
1132
-
1133
- def updateIndentationsUseTabs(self):
1134
- for index in range(self.uiWorkboxTAB.count()):
1135
- tab = self.uiWorkboxTAB.widget(index)
1136
- tab.setIndentationsUseTabs(self.uiIndentationsTabsACT.isChecked())
1137
-
1138
- def updatePdbVisibility(self, state):
1139
- self.uiPdbMENU.menuAction().setVisible(state)
1140
- self.uiPdbTOOLBAR.setVisible(state)
1141
- self.uiWorkboxSTACK.setCurrentIndex(1 if state else 0)
1142
-
1143
- def shutdown(self):
1144
- # close out of the ide system
1145
-
1146
- # if this is the global instance, then allow it to be deleted on close
1147
- if self == LoggerWindow._instance:
1148
- self.setAttribute(Qt.WA_DeleteOnClose, True)
1149
- LoggerWindow._instance = None
1150
-
1151
- # clear out the system
1152
- self.close()
1153
-
1154
- def workboxTabRightClick(self, pos):
1155
- self._currentTab = self.uiWorkboxTAB.tabBar().tabAt(pos)
1156
- if self._currentTab == -1:
1157
- return
1158
- menu = QMenu(self.uiWorkboxTAB.tabBar())
1159
- act = menu.addAction('Rename')
1160
- act.triggered.connect(self.renameTab)
1161
- act = menu.addAction('Link')
1162
- act.triggered.connect(self.linkCurrentTab)
1163
- act = menu.addAction('Un-Link')
1164
- act.triggered.connect(self.unlinkCurrentTab)
1165
-
1166
- menu.popup(QCursor.pos())
1167
-
1168
- def nextTab(self):
1169
- """Move focus to next workbox tab"""
1170
- tabWidget = self.uiWorkboxTAB
1171
- if not tabWidget.currentWidget().hasFocus():
1172
- tabWidget.currentWidget().setFocus()
1173
-
1174
- index = tabWidget.currentIndex()
1175
- if index == tabWidget.count() - 1:
1176
- tabWidget.setCurrentIndex(0)
1177
- else:
1178
- tabWidget.setCurrentIndex(index + 1)
1179
-
1180
- def prevTab(self):
1181
- """Move focus to previous workbox tab"""
1182
- tabWidget = self.uiWorkboxTAB
1183
- if not tabWidget.currentWidget().hasFocus():
1184
- tabWidget.currentWidget().setFocus()
1185
-
1186
- index = tabWidget.currentIndex()
1187
- if index == 0:
1188
- tabWidget.setCurrentIndex(tabWidget.count() - 1)
1189
- else:
1190
- tabWidget.setCurrentIndex(index - 1)
1191
-
1192
- def gotoTabByIndex(self, index):
1193
- """Generally to be used in conjunction with the Ctrl+<num> keyboard shortcuts,
1194
- which allow user to jump directly to another tab, mimicing we browser
1195
- functionality.
1196
- """
1197
- if index == -1:
1198
- index = self.uiWorkboxTAB.count() - 1
1199
- else:
1200
- count = self.uiWorkboxTAB.count()
1201
- index = min(index, count)
1202
- index -= 1
1203
-
1204
- self.uiWorkboxTAB.setCurrentIndex(index)
1205
-
1206
- def renameTab(self):
1207
- if self._currentTab != -1:
1208
- current = self.uiWorkboxTAB.tabBar().tabText(self._currentTab)
1209
- msg = 'Rename the {} tab to:'.format(current)
1210
- name, success = QInputDialog.getText(None, 'Rename Tab', msg, text=current)
1211
- if success:
1212
- self.uiWorkboxTAB.tabBar().setTabText(self._currentTab, name)
1213
-
1214
- def linkCurrentTab(self):
1215
- if self._currentTab != -1:
1216
- # get the previous path
1217
- pref = self.load_prefs()
1218
- prevPath = pref.get('linkFolder', os.path.join(os.path.expanduser('~')))
1219
-
1220
- # Handle the file dialog
1221
- filters = "Python Files (*.py);;All Files (*.*)"
1222
- path, _ = QtCompat.QFileDialog.getOpenFileName(
1223
- self, "Link File", prevPath, filters
1224
- )
1225
- if not path:
1226
- return
1227
-
1228
- pref['linkFolder'] = os.path.dirname(path)
1229
- self.save_prefs(pref)
1230
-
1231
- self.linkTab(self._currentTab, path)
1232
-
1233
- def linkTab(self, tabIdx, path):
1234
- wid = self.uiWorkboxTAB.widget(tabIdx)
1235
- tab = self.uiWorkboxTAB.tabBar()
1236
-
1237
- wid.load(path)
1238
- wid.setAutoReloadOnChange(True)
1239
- tab.setTabText(tabIdx, os.path.basename(path))
1240
- tab.setTabToolTip(tabIdx, path)
1241
- iconprovider = QFileIconProvider()
1242
- tab.setTabIcon(tabIdx, iconprovider.icon(QFileInfo(path)))
1243
-
1244
- font = self.console().font()
1245
- wid.setWorkboxFont(font)
1246
-
1247
- def unlinkCurrentTab(self):
1248
- if self._currentTab != -1:
1249
- self.unlinkTab(self._currentTab)
1250
-
1251
- def unlinkTab(self, tabIdx):
1252
- wid = self.uiWorkboxTAB.currentWidget()
1253
- tab = self.uiWorkboxTAB.tabBar()
1254
-
1255
- wid.enableFileWatching(False)
1256
- wid.setAutoReloadOnChange(False)
1257
- tab.setTabToolTip(tabIdx, '')
1258
- tab.setTabIcon(tabIdx, QIcon())
1259
-
1260
- def linkedFileChanged(self, filename):
1261
- font = self.console().font()
1262
- for tabIndex in range(self.uiWorkboxTAB.count()):
1263
- workbox = self.uiWorkboxTAB.widget(tabIndex)
1264
- if workbox.filename() == filename:
1265
- self._reloadRequested.add(tabIndex)
1266
- self.uiWorkboxTAB.currentWidget().setFont(font)
1267
-
1268
- newIdx = self.uiWorkboxTAB.currentIndex()
1269
- self.updateLink(newIdx)
1270
-
1271
- def currentChanged(self):
1272
- newIdx = self.uiWorkboxTAB.currentIndex()
1273
- self.updateLink(newIdx)
1274
-
1275
- def updateLink(self, tabIdx):
1276
- if tabIdx in self._reloadRequested:
1277
- fn = self.uiWorkboxTAB.currentWidget().filename()
1278
- if not os.path.isfile(fn):
1279
- self.unlinkTab(tabIdx)
1280
- else:
1281
- # Only reload the current widget if requested
1282
- time.sleep(0.1) # loading the file too quickly misses any changes
1283
- self.uiWorkboxTAB.currentWidget().reloadChange()
1284
- font = self.console().font()
1285
- self.uiWorkboxTAB.currentWidget().setFont(font)
1286
- self._reloadRequested.remove(tabIdx)
1287
-
1288
- def openFileMonitor(self):
1289
- return self._openFileMonitor
1290
-
1291
- @staticmethod
1292
- def instance(parent=None, runWorkbox=False, create=True):
1293
- """Returns the existing instance of the python logger creating it on first call.
1294
-
1295
- Args:
1296
- parent (QWidget, optional): If the instance hasn't been created yet, create
1297
- it and parent it to this object.
1298
- runWorkbox (bool, optional): If the instance hasn't been created yet, this
1299
- will execute the active workbox's code once fully initialized.
1300
- create (bool, optional): Returns None if the instance has not been created.
1301
-
1302
- Returns:
1303
- Returns a fully initialized instance of the Python Logger. If called more
1304
- than once, the same instance will be returned. If create is False, it may
1305
- return None.
1306
- """
1307
- # create the instance for the logger
1308
- if not LoggerWindow._instance:
1309
- if not create:
1310
- return None
1311
-
1312
- # create the logger instance
1313
- inst = LoggerWindow(parent, runWorkbox=runWorkbox)
1314
-
1315
- # RV has a Unique window structure. It makes more sense to not parent a
1316
- # singleton window than to parent it to a specific top level window.
1317
- if blurdev.core.objectName() == 'rv':
1318
- inst.setParent(None)
1319
- inst.setAttribute(Qt.WA_QuitOnClose, False)
1320
-
1321
- # protect the memory
1322
- inst.setAttribute(Qt.WA_DeleteOnClose, False)
1323
-
1324
- # cache the instance
1325
- LoggerWindow._instance = inst
1326
-
1327
- return LoggerWindow._instance
1328
-
1329
- def installLogToFile(self):
1330
- """All stdout/stderr output is also appended to this file.
1331
-
1332
- This uses blurdev.debug.logToFile(path, useOldStd=True).
1333
- """
1334
- if self._logToFilePath is None:
1335
- path = blurdev.osystem.defaultLogFile()
1336
- path, _ = QtCompat.QFileDialog.getOpenFileName(
1337
- self, "Log Output to File", path
1338
- )
1339
- if not path:
1340
- return
1341
- path = os.path.normpath(path)
1342
- print('Output logged to: "{}"'.format(path))
1343
- blurdev.debug.logToFile(path, useOldStd=True)
1344
- # Store the std's so we can clear them later
1345
- self._stds = (sys.stdout, sys.stderr)
1346
- self.uiLogToFileACT.setText('Output Logged to File')
1347
- self.uiLogToFileClearACT.setVisible(True)
1348
- self._logToFilePath = path
1349
- else:
1350
- print('Output logged to: "{}"'.format(self._logToFilePath))
1351
-
1352
- @classmethod
1353
- def instanceSetPdbMode(cls, mode, msg=''):
1354
- """Sets the instance of LoggerWindow to pdb mode if the logger instance has
1355
- been created.
1356
-
1357
- Args:
1358
- mode (bool): The mode to set it to
1359
- """
1360
- if cls._instance:
1361
- inst = cls._instance
1362
- if inst.uiConsoleTXT.pdbMode() != mode:
1363
- inst.uiConsoleTXT.setPdbMode(mode)
1364
- import blurdev.external
1365
-
1366
- blurdev.external.External(
1367
- ['pdb', '', {'msg': 'blurdev.debug.getPdb().currentLine()'}]
1368
- )
1369
- # Pdb returns its prompt automatically. If we detect the pdb prompt and
1370
- # _pdbContinue is set re-run the command until it's count reaches zero.
1371
- if inst._pdbContinue and msg == '(Pdb) ':
1372
- if inst._pdbContinue[0]:
1373
- count = inst._pdbContinue[0] - 1
1374
- if count > 0:
1375
- # Decrease the count.
1376
- inst._pdbContinue = (count, inst._pdbContinue[1])
1377
- # Resend the requested message
1378
- inst.uiConsoleTXT.pdbSendCommand(inst._pdbContinue[1])
1379
- else:
1380
- # We are done refreshing so nothing to do.
1381
- inst._pdbContinue = None
1382
-
1383
- @classmethod
1384
- def instancePdbResult(cls, data):
1385
- if cls._instance:
1386
- if data.get('msg') == 'pdb_currentLine':
1387
- filename = data.get('filename')
1388
- lineNo = data.get('lineNo')
1389
- doc = cls._instance.uiPdbTAB.currentWidget()
1390
- if not isinstance(doc, WorkboxWidget):
1391
- doc = cls._instance.addWorkbox(
1392
- cls._instance.uiPdbTAB, closable=False
1393
- )
1394
- cls._instance._pdb_marker = doc.markerDefine(doc.Circle)
1395
- cls._instance.uiPdbTAB.setTabText(
1396
- cls._instance.uiPdbTAB.currentIndex(), filename
1397
- )
1398
- doc.markerDeleteAll(cls._instance._pdb_marker)
1399
- if os.path.exists(filename):
1400
- doc.load(filename)
1401
- doc.goToLine(lineNo)
1402
- doc.markerAdd(lineNo, cls._instance._pdb_marker)
1403
- else:
1404
- doc.clear()
1405
- doc._filename = ''
1406
-
1407
- @classmethod
1408
- def instanceShutdown(cls):
1409
- """Faster way to shutdown the instance of LoggerWindow if it possibly was not used.
1410
-
1411
- Returns:
1412
- bool: If a shutdown was required
1413
- """
1414
- if cls._instance:
1415
- cls._instance.shutdown()
1416
- return True
1417
- return False
1
+ from __future__ import absolute_import, print_function
2
+
3
+ import itertools
4
+ import json
5
+ import os
6
+ import re
7
+ import sys
8
+ import warnings
9
+ from builtins import bytes
10
+ from datetime import datetime, timedelta
11
+ from functools import partial
12
+
13
+ import __main__
14
+ import six
15
+ from Qt import QtCompat, QtCore, QtWidgets
16
+ from Qt.QtCore import QByteArray, Qt, QTimer, Signal, Slot
17
+ from Qt.QtGui import QCursor, QFont, QFontDatabase, QIcon, QTextCursor
18
+ from Qt.QtWidgets import (
19
+ QApplication,
20
+ QInputDialog,
21
+ QLabel,
22
+ QMessageBox,
23
+ QTextBrowser,
24
+ QToolTip,
25
+ QVBoxLayout,
26
+ )
27
+
28
+ from .. import (
29
+ DEFAULT_CORE_NAME,
30
+ about_preditor,
31
+ core,
32
+ debug,
33
+ osystem,
34
+ plugins,
35
+ prefs,
36
+ resourcePath,
37
+ )
38
+ from ..delayable_engine import DelayableEngine
39
+ from ..gui import Dialog, Window, loadUi
40
+ from ..gui.fuzzy_search.fuzzy_search import FuzzySearch
41
+ from ..gui.group_tab_widget.grouped_tab_models import GroupTabListItemModel
42
+ from ..logging_config import LoggingConfig
43
+ from ..utils import stylesheets
44
+ from .completer import CompleterMode
45
+ from .level_buttons import LoggingLevelButton
46
+ from .set_text_editor_path_dialog import SetTextEditorPathDialog
47
+
48
+
49
+ class WorkboxPages:
50
+ """Nice names for the uiWorkboxSTACK indexes."""
51
+
52
+ Options = 0
53
+ Workboxes = 1
54
+
55
+
56
+ class LoggerWindow(Window):
57
+ _instance = None
58
+ styleSheetChanged = Signal(str)
59
+
60
+ def __init__(self, parent, name=None, run_workbox=False, standalone=False):
61
+ super(LoggerWindow, self).__init__(parent=parent)
62
+ self.name = name if name else DEFAULT_CORE_NAME
63
+ self.aboutToClearPathsEnabled = False
64
+ self._stylesheet = 'Bright'
65
+
66
+ # Create timer to autohide status messages
67
+ self.statusTimer = QTimer()
68
+ self.statusTimer.setSingleShot(True)
69
+
70
+ # Store the previous time a font-resize wheel event was triggered to prevent
71
+ # rapid-fire WheelEvents. Initialize to the current time.
72
+ self.previousFontResizeTime = datetime.now()
73
+
74
+ self.setWindowIcon(QIcon(resourcePath('img/preditor.png')))
75
+ loadUi(__file__, self)
76
+
77
+ self.uiConsoleTXT.flash_window = self
78
+ self.uiConsoleTXT.reportExecutionTime = self.reportExecutionTime
79
+ self.uiClearToLastPromptACT.triggered.connect(
80
+ self.uiConsoleTXT.clearToLastPrompt
81
+ )
82
+ # If we don't disable this shortcut Qt won't respond to this classes or
83
+ # the ConsolePrEdit's
84
+ self.uiConsoleTXT.uiClearToLastPromptACT.setShortcut('')
85
+
86
+ # create the status reporting label
87
+ self.uiStatusLBL = QLabel(self)
88
+ self.uiMenuBar.setCornerWidget(self.uiStatusLBL)
89
+
90
+ # create the workbox tabs
91
+ self._currentTab = -1
92
+ self._reloadRequested = set()
93
+ # Setup delayable system
94
+ self.delayable_engine = DelayableEngine.instance('logger', self)
95
+
96
+ self.uiWorkboxTAB.editor_kwargs = dict(
97
+ console=self.uiConsoleTXT, delayable_engine=self.delayable_engine.name
98
+ )
99
+
100
+ # Create additional buttons in toolbar.
101
+ self.uiLoggingLevelBTN = LoggingLevelButton(self)
102
+ self.uiConsoleTOOLBAR.insertWidget(
103
+ self.uiRunSelectedACT,
104
+ self.uiLoggingLevelBTN,
105
+ )
106
+ self.uiConsoleTOOLBAR.insertSeparator(self.uiRunSelectedACT)
107
+
108
+ # Initial configuration of the logToFile feature
109
+ self._logToFilePath = None
110
+ self._stds = None
111
+ self.uiLogToFileClearACT.setVisible(False)
112
+
113
+ self.uiRestartACT.triggered.connect(self.restartLogger)
114
+ self.uiCloseLoggerACT.triggered.connect(self.closeLogger)
115
+
116
+ self.uiRunAllACT.triggered.connect(self.execAll)
117
+ self.uiRunSelectedACT.triggered.connect(self.execSelected)
118
+
119
+ self.uiAutoCompleteEnabledACT.toggled.connect(self.setAutoCompleteEnabled)
120
+
121
+ self.uiAutoCompleteCaseSensitiveACT.toggled.connect(self.setCaseSensitive)
122
+
123
+ # Setup ability to cycle completer mode, and create action for each mode
124
+ self.completerModeCycle = itertools.cycle(CompleterMode)
125
+ # create CompleterMode submenu
126
+ defaultMode = next(self.completerModeCycle)
127
+ for mode in CompleterMode:
128
+ modeName = mode.displayName()
129
+ action = self.uiCompleterModeMENU.addAction(modeName)
130
+ action.setObjectName('ui{}ModeACT'.format(modeName))
131
+ action.setData(mode)
132
+ action.setCheckable(True)
133
+ action.setChecked(mode == defaultMode)
134
+ completerMode = CompleterMode(mode)
135
+ action.setToolTip(completerMode.toolTip())
136
+ action.triggered.connect(partial(self.selectCompleterMode, action))
137
+
138
+ self.uiCompleterModeMENU.addSeparator()
139
+ action = self.uiCompleterModeMENU.addAction('Cycle mode')
140
+ action.setObjectName('uiCycleModeACT')
141
+ action.setShortcut(Qt.CTRL | Qt.Key_M)
142
+ action.triggered.connect(self.cycleCompleterMode)
143
+ self.uiCompleterModeMENU.hovered.connect(self.handleMenuHovered)
144
+
145
+ # Workbox add/remove
146
+ self.uiNewWorkboxACT.triggered.connect(
147
+ lambda: self.uiWorkboxTAB.add_new_tab(group=True)
148
+ )
149
+ self.uiCloseWorkboxACT.triggered.connect(self.uiWorkboxTAB.close_current_tab)
150
+
151
+ # Browse previous commands
152
+ self.uiGetPrevCmdACT.triggered.connect(self.getPrevCommand)
153
+ self.uiGetNextCmdACT.triggered.connect(self.getNextCommand)
154
+
155
+ # Focus to console or to workbox, optionally copy seleciton or line
156
+ self.uiFocusToConsoleACT.triggered.connect(self.focusToConsole)
157
+ self.uiCopyToConsoleACT.triggered.connect(self.copyToConsole)
158
+ self.uiFocusToWorkboxACT.triggered.connect(self.focusToWorkbox)
159
+ self.uiCopyToWorkboxACT.triggered.connect(self.copyToWorkbox)
160
+
161
+ # Navigate workbox tabs
162
+ self.uiNextTabACT.triggered.connect(self.nextTab)
163
+ self.uiPrevTabACT.triggered.connect(self.prevTab)
164
+
165
+ self.uiTab1ACT.triggered.connect(partial(self.gotoTabByIndex, 1))
166
+ self.uiTab2ACT.triggered.connect(partial(self.gotoTabByIndex, 2))
167
+ self.uiTab3ACT.triggered.connect(partial(self.gotoTabByIndex, 3))
168
+ self.uiTab4ACT.triggered.connect(partial(self.gotoTabByIndex, 4))
169
+ self.uiTab5ACT.triggered.connect(partial(self.gotoTabByIndex, 5))
170
+ self.uiTab6ACT.triggered.connect(partial(self.gotoTabByIndex, 6))
171
+ self.uiTab7ACT.triggered.connect(partial(self.gotoTabByIndex, 7))
172
+ self.uiTab8ACT.triggered.connect(partial(self.gotoTabByIndex, 8))
173
+ self.uiTabLastACT.triggered.connect(partial(self.gotoTabByIndex, -1))
174
+
175
+ self.uiGroup1ACT.triggered.connect(partial(self.gotoGroupByIndex, 1))
176
+ self.uiGroup2ACT.triggered.connect(partial(self.gotoGroupByIndex, 2))
177
+ self.uiGroup3ACT.triggered.connect(partial(self.gotoGroupByIndex, 3))
178
+ self.uiGroup4ACT.triggered.connect(partial(self.gotoGroupByIndex, 4))
179
+ self.uiGroup5ACT.triggered.connect(partial(self.gotoGroupByIndex, 5))
180
+ self.uiGroup6ACT.triggered.connect(partial(self.gotoGroupByIndex, 6))
181
+ self.uiGroup7ACT.triggered.connect(partial(self.gotoGroupByIndex, 7))
182
+ self.uiGroup8ACT.triggered.connect(partial(self.gotoGroupByIndex, 8))
183
+ self.uiGroupLastACT.triggered.connect(partial(self.gotoGroupByIndex, -1))
184
+
185
+ self.uiFocusNameACT.triggered.connect(self.show_focus_name)
186
+
187
+ self.uiCommentToggleACT.triggered.connect(self.comment_toggle)
188
+
189
+ self.uiSpellCheckEnabledACT.toggled.connect(self.setSpellCheckEnabled)
190
+ self.uiIndentationsTabsACT.toggled.connect(self.updateIndentationsUseTabs)
191
+ self.uiCopyTabsToSpacesACT.toggled.connect(self.updateCopyIndentsAsSpaces)
192
+ self.uiWordWrapACT.toggled.connect(self.setWordWrap)
193
+ self.uiResetWarningFiltersACT.triggered.connect(warnings.resetwarnings)
194
+ self.uiLogToFileACT.triggered.connect(self.installLogToFile)
195
+ self.uiLogToFileClearACT.triggered.connect(self.clearLogToFile)
196
+ self.uiClearLogACT.triggered.connect(self.clearLog)
197
+ self.uiSaveConsoleSettingsACT.triggered.connect(
198
+ lambda: self.recordPrefs(manual=True)
199
+ )
200
+ self.uiClearBeforeRunningACT.triggered.connect(self.setClearBeforeRunning)
201
+ self.uiEditorVerticalACT.toggled.connect(self.adjustWorkboxOrientation)
202
+ self.uiEnvironmentVarsACT.triggered.connect(self.showEnvironmentVars)
203
+ self.uiBackupPreferencesACT.triggered.connect(self.backupPreferences)
204
+ self.uiBrowsePreferencesACT.triggered.connect(self.browsePreferences)
205
+ self.uiAboutPreditorACT.triggered.connect(self.show_about)
206
+ core.aboutToClearPaths.connect(self.pathsAboutToBeCleared)
207
+ self.uiSetFlashWindowIntervalACT.triggered.connect(self.setFlashWindowInterval)
208
+
209
+ self.uiSetPreferredTextEditorPathACT.triggered.connect(
210
+ self.openSetPreferredTextEditorDialog
211
+ )
212
+
213
+ # Tooltips - Qt4 doesn't have a ToolTipsVisible method, so we fake it
214
+ regEx = ".*"
215
+ menus = self.findChildren(QtWidgets.QMenu, QtCore.QRegExp(regEx))
216
+ for menu in menus:
217
+ menu.hovered.connect(self.handleMenuHovered)
218
+
219
+ self.uiClearLogACT.setIcon(QIcon(resourcePath('img/close-thick.png')))
220
+ self.uiNewWorkboxACT.setIcon(QIcon(resourcePath('img/file-plus.png')))
221
+ self.uiCloseWorkboxACT.setIcon(QIcon(resourcePath('img/file-remove.png')))
222
+ self.uiSaveConsoleSettingsACT.setIcon(
223
+ QIcon(resourcePath('img/content-save.png'))
224
+ )
225
+ self.uiAboutPreditorACT.setIcon(QIcon(resourcePath('img/information.png')))
226
+ self.uiRestartACT.setIcon(QIcon(resourcePath('img/restart.svg')))
227
+ self.uiCloseLoggerACT.setIcon(QIcon(resourcePath('img/close-thick.png')))
228
+
229
+ # Make action shortcuts available anywhere in the Logger
230
+ self.addAction(self.uiClearLogACT)
231
+
232
+ self.restorePrefs()
233
+
234
+ # add font menu list
235
+ curFamily = self.console().font().family()
236
+ fontDB = QFontDatabase()
237
+ fontFamilies = fontDB.families(QFontDatabase.Latin)
238
+ monospaceFonts = [fam for fam in fontFamilies if fontDB.isFixedPitch(fam)]
239
+
240
+ self.uiMonospaceFontMENU.clear()
241
+ self.uiProportionalFontMENU.clear()
242
+
243
+ for family in fontFamilies:
244
+ if family in monospaceFonts:
245
+ action = self.uiMonospaceFontMENU.addAction(family)
246
+ else:
247
+ action = self.uiProportionalFontMENU.addAction(family)
248
+ action.setObjectName(u'ui{}FontACT'.format(family))
249
+ action.setCheckable(True)
250
+ action.setChecked(family == curFamily)
251
+ action.triggered.connect(partial(self.selectFont, action))
252
+
253
+ # add stylesheet menu options.
254
+ for style_name in stylesheets.stylesheets():
255
+ action = self.uiStyleMENU.addAction(style_name)
256
+ action.setObjectName('ui{}ACT'.format(style_name))
257
+ action.setCheckable(True)
258
+ action.setChecked(self._stylesheet == style_name)
259
+ action.triggered.connect(partial(self.setStyleSheet, style_name))
260
+
261
+ self.uiConsoleTOOLBAR.show()
262
+ loggerName = QApplication.instance().translate(
263
+ 'PrEditorWindow', DEFAULT_CORE_NAME
264
+ )
265
+ self.setWindowTitle(
266
+ '{} - {} - {} {}-bit'.format(
267
+ loggerName,
268
+ self.name,
269
+ '{}.{}.{}'.format(*sys.version_info[:3]),
270
+ osystem.getPointerSize(),
271
+ )
272
+ )
273
+
274
+ self.setup_run_workbox()
275
+
276
+ if not standalone:
277
+ # This action only is valid when running in standalone mode
278
+ self.uiRestartACT.setVisible(False)
279
+
280
+ # Run the current workbox after the LoggerWindow is shown.
281
+ if run_workbox:
282
+ # By using two singleShot timers, we can show and draw the LoggerWindow,
283
+ # then call execAll. This makes it easier to see what code you are running
284
+ # before it has finished running completely.
285
+ # QTimer.singleShot(0, lambda: QTimer.singleShot(0, self.execAll))
286
+ QTimer.singleShot(
287
+ 0, lambda: QTimer.singleShot(0, lambda: self.run_workbox(run_workbox))
288
+ )
289
+
290
+ @Slot()
291
+ def apply_options(self):
292
+ """Apply editor options the user chose on the WorkboxPage.Options page."""
293
+ editor_cls_name, editor_cls = plugins.editor(
294
+ self.uiEditorChooserWGT.editor_name()
295
+ )
296
+ if editor_cls_name is None:
297
+ return
298
+ if editor_cls_name != self.editor_cls_name:
299
+ self.editor_cls_name = editor_cls_name
300
+ self.uiWorkboxTAB.editor_cls = editor_cls
301
+ # We need to change the editor, save all prefs
302
+ self.recordPrefs()
303
+ # Clear the uiWorkboxTAB
304
+ self.uiWorkboxTAB.clear()
305
+ # Restore prefs to populate the tabs
306
+ self.restorePrefs()
307
+
308
+ self.update_workbox_stack()
309
+
310
+ def comment_toggle(self):
311
+ self.current_workbox().__comment_toggle__()
312
+
313
+ def current_workbox(self):
314
+ """Returns the current workbox for the current tab group."""
315
+ return self.uiWorkboxTAB.current_groups_widget()
316
+
317
+ @classmethod
318
+ def name_for_workbox(cls, workbox):
319
+ """Returns the name for a given workbox.
320
+ The name is the group tab text and the workbox tab text joined by a `/`"""
321
+ ret = []
322
+ logger = cls.instance()
323
+ index = logger.uiWorkboxTAB.currentIndex()
324
+ ret.append(logger.uiWorkboxTAB.tabText(index))
325
+ group_widget = logger.uiWorkboxTAB.currentWidget()
326
+ index = group_widget.currentIndex()
327
+ ret.append(group_widget.tabText(index))
328
+ return "/".join(ret)
329
+
330
+ @classmethod
331
+ def workbox_for_name(cls, name, show=False, visible=False):
332
+ """Used to find a workbox for a given name. It accepts a string matching
333
+ the "{group}/{workbox}" format, or if True, the current workbox.
334
+
335
+ Args:
336
+ name(str, boolean): Used to define which workbox to run.
337
+ show (bool, optional): If a workbox is found, call `__show__` on it
338
+ to ensure that it is initialized and its text is loaded.
339
+ visible (bool, optional): Make the this workbox visible if found.
340
+ """
341
+ logger = cls.instance()
342
+
343
+ workbox = None
344
+
345
+ # If name is True, run the current workbox
346
+ if isinstance(name, bool):
347
+ if name:
348
+ workbox = logger.current_workbox()
349
+
350
+ # If name is a string, find first tab with that name
351
+ elif isinstance(name, six.string_types):
352
+ split = name.split('/', 1)
353
+ if len(split) < 2:
354
+ return None
355
+ group, editor = split
356
+ group_index = logger.uiWorkboxTAB.index_for_text(group)
357
+ if group_index != -1:
358
+ tab_widget = logger.uiWorkboxTAB.widget(group_index)
359
+ index = tab_widget.index_for_text(editor)
360
+ if index != -1:
361
+ workbox = tab_widget.widget(index)
362
+ if visible:
363
+ tab_widget.setCurrentIndex(index)
364
+ logger.uiWorkboxTAB.setCurrentIndex(group_index)
365
+
366
+ if show and workbox:
367
+ workbox.__show__()
368
+
369
+ return workbox
370
+
371
+ @classmethod
372
+ def run_workbox(cls, name):
373
+ """This is a function which will be added to __main__, and therefore
374
+ available to PythonLogger users. It will accept a string matching the
375
+ "{group}/{workbox}" format, or a boolean that will run the current tab
376
+ to support the command line launching functionality which auto-runs the
377
+ current workbox on launch.
378
+
379
+ Args:
380
+ name(str, boolean): Used to define which workbox to run.
381
+
382
+ Raises:
383
+ Exception: "Cannot call current workbox."
384
+
385
+ Example Usages:
386
+ run_workbox('group_a/test')
387
+ run_workbox('some/stuff.py')
388
+ (from command line): blurdev launch Python_Logger --run_workbox
389
+ """
390
+ workbox = cls.workbox_for_name(name)
391
+
392
+ if workbox is not None:
393
+ # if name is True, its ok to run the workbox, this option
394
+ # is passed by the cli to run the current tab
395
+ if workbox.hasFocus() and name is not True:
396
+ raise Exception("Cannot call current workbox.")
397
+ else:
398
+ # Make sure the workbox text is loaded as it likely has not
399
+ # been shown yet and each tab is now loaded only on demand.
400
+ workbox.__show__()
401
+ workbox.__exec_all__()
402
+
403
+ def setup_run_workbox(self):
404
+ """We will bind the runWordbox function on __main__, which makes is available to
405
+ code running within PythonLogger.
406
+ """
407
+ __main__.run_workbox = self.run_workbox
408
+
409
+ def openSetPreferredTextEditorDialog(self):
410
+ dlg = SetTextEditorPathDialog(parent=self)
411
+ dlg.exec_()
412
+
413
+ def focusToConsole(self):
414
+ """Move focus to the console"""
415
+ self.console().setFocus()
416
+
417
+ def focusToWorkbox(self):
418
+ """Move focus to the current workbox"""
419
+ self.current_workbox().setFocus()
420
+
421
+ def copyToConsole(self):
422
+ """Copy current selection or line from workbox to console"""
423
+ workbox = self.current_workbox()
424
+ if not workbox.hasFocus():
425
+ return
426
+
427
+ text = workbox.__selected_text__()
428
+ if not text:
429
+ line, index = workbox.__cursor_position__()
430
+ text = workbox.__text__(line)
431
+ text = text.rstrip('\r\n')
432
+ if not text:
433
+ return
434
+
435
+ cursor = self.console().textCursor()
436
+ if cursor.hasSelection():
437
+ cursor.removeSelectedText()
438
+
439
+ self.console().insertPlainText(text)
440
+ self.focusToConsole()
441
+
442
+ def copyToWorkbox(self):
443
+ """Copy current selection or line from console to workbox"""
444
+ console = self.console()
445
+ if not console.hasFocus():
446
+ return
447
+
448
+ cursor = console.textCursor()
449
+ if not cursor.hasSelection():
450
+ cursor.select(QTextCursor.LineUnderCursor)
451
+ text = cursor.selectedText()
452
+ prompt = console.prompt()
453
+ if text.startswith(prompt):
454
+ text = text[len(prompt) :]
455
+ text = text.lstrip()
456
+
457
+ outputPrompt = console.outputPrompt()
458
+ outputPrompt = outputPrompt.rstrip()
459
+ if text.startswith(outputPrompt):
460
+ text = text[len(outputPrompt) :]
461
+ text = text.lstrip()
462
+
463
+ if not text:
464
+ return
465
+
466
+ workbox = self.current_workbox()
467
+ workbox.__remove_selected_text__()
468
+ workbox.__insert_text__(text)
469
+
470
+ line, index = workbox.__cursor_position__()
471
+ index += len(text)
472
+ workbox.__set_cursor_position__(line, index)
473
+
474
+ self.focusToWorkbox()
475
+
476
+ def getNextCommand(self):
477
+ if hasattr(self.console(), 'getNextCommand'):
478
+ self.console().getNextCommand()
479
+
480
+ def getPrevCommand(self):
481
+ if hasattr(self.console(), 'getPrevCommand'):
482
+ self.console().getPrevCommand()
483
+
484
+ def wheelEvent(self, event):
485
+ """adjust font size on ctrl+scrollWheel"""
486
+ if event.modifiers() == Qt.ControlModifier:
487
+ # WheelEvents can be emitted in a cluster, but we only want one at a time
488
+ # (ie to change font size by 1, rather than 2 or 3). Let's bail if previous
489
+ # font-resize wheel event was within a certain threshhold.
490
+ now = datetime.now()
491
+ elapsed = now - self.previousFontResizeTime
492
+ tolerance = timedelta(microseconds=100000)
493
+ if elapsed < tolerance:
494
+ return
495
+ self.previousFontResizeTime = now
496
+
497
+ # QT4 presents QWheelEvent.delta(), QT5 has QWheelEvent.angleDelta().y()
498
+ if hasattr(event, 'delta'): # Qt4
499
+ delta = event.delta()
500
+ else: # QT5
501
+ delta = event.angleDelta().y()
502
+
503
+ # convert delta to +1 or -1, depending
504
+ delta = delta / abs(delta)
505
+ minSize = 5
506
+ maxSize = 50
507
+ font = self.console().font()
508
+ newSize = font.pointSize() + delta
509
+ newSize = max(min(newSize, maxSize), minSize)
510
+
511
+ font.setPointSize(newSize)
512
+ self.console().setConsoleFont(font)
513
+
514
+ for workbox in self.uiWorkboxTAB.all_widgets():
515
+ marginsFont = workbox.__margins_font__()
516
+ marginsFont.setPointSize(newSize)
517
+ workbox.__set_margins_font__(marginsFont)
518
+
519
+ workbox.__set_font__(font)
520
+ else:
521
+ Window.wheelEvent(self, event)
522
+
523
+ def handleMenuHovered(self, action):
524
+ """Qt4 doesn't have a ToolTipsVisible method, so we fake it"""
525
+ # Don't show if it's just the text of the action
526
+ text = re.sub(r"(?<!&)&(?!&)", "", action.text())
527
+ text = text.replace('...', '')
528
+
529
+ if text == action.toolTip():
530
+ text = ''
531
+ else:
532
+ text = action.toolTip()
533
+
534
+ menu = action.parentWidget()
535
+ QToolTip.showText(QCursor.pos(), text, menu)
536
+
537
+ def findCurrentFontAction(self):
538
+ """Find and return current font's action"""
539
+ actions = self.uiMonospaceFontMENU.actions()
540
+ actions.extend(self.uiProportionalFontMENU.actions())
541
+
542
+ action = None
543
+ for act in actions:
544
+ if act.isChecked():
545
+ action = act
546
+ break
547
+
548
+ return action
549
+
550
+ def selectFont(self, action):
551
+ """Set console and workbox font to current font
552
+
553
+ Args:
554
+ action (QAction): menu action associated with chosen font
555
+ """
556
+
557
+ actions = self.uiMonospaceFontMENU.actions()
558
+ actions.extend(self.uiProportionalFontMENU.actions())
559
+
560
+ for act in actions:
561
+ act.setChecked(act == action)
562
+
563
+ family = action.text()
564
+ font = self.console().font()
565
+ font.setFamily(family)
566
+ self.console().setConsoleFont(font)
567
+
568
+ for workbox in self.uiWorkboxTAB.all_widgets():
569
+ workbox.__set_margins_font__(font)
570
+ workbox.__set_font__(font)
571
+
572
+ @classmethod
573
+ def _genPrefName(cls, baseName, index):
574
+ if index:
575
+ baseName = '{name}{index}'.format(name=baseName, index=index)
576
+ return baseName
577
+
578
+ def adjustWorkboxOrientation(self, state):
579
+ if state:
580
+ self.uiSplitterSPLIT.setOrientation(Qt.Horizontal)
581
+ else:
582
+ self.uiSplitterSPLIT.setOrientation(Qt.Vertical)
583
+
584
+ def backupPreferences(self):
585
+ """Saves a copy of the current preferences to a zip archive."""
586
+ zip_path = prefs.backup()
587
+ print('PrEditor Preferences backed up to "{}"'.format(zip_path))
588
+ return zip_path
589
+
590
+ def browsePreferences(self):
591
+ prefs.browse(core_name=self.name)
592
+
593
+ def console(self):
594
+ return self.uiConsoleTXT
595
+
596
+ def clearLog(self):
597
+ self.uiConsoleTXT.clear()
598
+
599
+ def clearLogToFile(self):
600
+ """If installLogToFile has been called, clear the stdout."""
601
+ if self._stds:
602
+ self._stds[0].clear(stamp=True)
603
+
604
+ def closeEvent(self, event):
605
+ self.recordPrefs()
606
+ # Save the logger configuration
607
+ lcfg = LoggingConfig(core_name=self.name)
608
+ lcfg.build()
609
+ lcfg.save()
610
+
611
+ super(LoggerWindow, self).closeEvent(event)
612
+ if self.uiConsoleTOOLBAR.isFloating():
613
+ self.uiConsoleTOOLBAR.hide()
614
+
615
+ # Handle any cleanup each workbox tab may need to do before closing
616
+ for editor, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
617
+ editor.__close__()
618
+
619
+ def closeLogger(self):
620
+ self.close()
621
+
622
+ def execAll(self):
623
+ """Clears the console before executing all workbox code"""
624
+ if self.uiClearBeforeRunningACT.isChecked():
625
+ self.clearLog()
626
+ self.current_workbox().__exec_all__()
627
+
628
+ if self.uiAutoPromptACT.isChecked():
629
+ console = self.console()
630
+ prompt = console.prompt()
631
+ console.startPrompt(prompt)
632
+
633
+ def execSelected(self):
634
+ """Clears the console before executing selected workbox code"""
635
+ if self.uiClearBeforeRunningACT.isChecked():
636
+ self.clearLog()
637
+ self.current_workbox().__exec_selected__()
638
+
639
+ def keyPressEvent(self, event):
640
+ # Fix 'Maya : Qt tools lose focus' https://redmine.blur.com/issues/34430
641
+ if event.modifiers() & (Qt.AltModifier | Qt.ControlModifier | Qt.ShiftModifier):
642
+ pass
643
+ else:
644
+ super(LoggerWindow, self).keyPressEvent(event)
645
+
646
+ def pathsAboutToBeCleared(self):
647
+ if self.uiClearLogOnRefreshACT.isChecked():
648
+ self.clearLog()
649
+
650
+ def reportExecutionTime(self, seconds):
651
+ """Update status text with seconds passed in."""
652
+ self.setStatusText('Exec: {:0.04f} Seconds'.format(seconds))
653
+
654
+ def recordPrefs(self, manual=False):
655
+ if not manual and not self.uiAutoSaveSettingssACT.isChecked():
656
+ return
657
+
658
+ pref = self.load_prefs()
659
+ geo = self.geometry()
660
+ pref.update(
661
+ {
662
+ 'loggergeom': [geo.x(), geo.y(), geo.width(), geo.height()],
663
+ 'windowState': int(self.windowState()),
664
+ 'SplitterVertical': self.uiEditorVerticalACT.isChecked(),
665
+ 'SplitterSize': self.uiSplitterSPLIT.sizes(),
666
+ 'tabIndent': self.uiIndentationsTabsACT.isChecked(),
667
+ 'copyIndentsAsSpaces': self.uiCopyTabsToSpacesACT.isChecked(),
668
+ 'hintingEnabled': self.uiAutoCompleteEnabledACT.isChecked(),
669
+ 'spellCheckEnabled': self.uiSpellCheckEnabledACT.isChecked(),
670
+ 'wordWrap': self.uiWordWrapACT.isChecked(),
671
+ 'clearBeforeRunning': self.uiClearBeforeRunningACT.isChecked(),
672
+ 'clearBeforeEnvRefresh': self.uiClearLogOnRefreshACT.isChecked(),
673
+ 'toolbarStates': six.text_type(self.saveState().toHex(), 'utf-8'),
674
+ 'consoleFont': self.console().font().toString(),
675
+ 'uiAutoSaveSettingssACT': self.uiAutoSaveSettingssACT.isChecked(),
676
+ 'uiAutoPromptACT': self.uiAutoPromptACT.isChecked(),
677
+ 'uiLinesInNewWorkboxACT': self.uiLinesInNewWorkboxACT.isChecked(),
678
+ 'uiErrorHyperlinksACT': self.uiErrorHyperlinksACT.isChecked(),
679
+ 'textEditorPath': self.textEditorPath,
680
+ 'textEditorCmdTempl': self.textEditorCmdTempl,
681
+ 'currentStyleSheet': self._stylesheet,
682
+ 'flash_time': self.uiConsoleTXT.flash_time,
683
+ }
684
+ )
685
+
686
+ # completer settings
687
+ completer = self.console().completer()
688
+ pref["caseSensitive"] = completer.caseSensitive()
689
+ pref["completerMode"] = completer.completerMode().value
690
+
691
+ if self._stylesheet == 'Custom':
692
+ pref['styleSheet'] = self.styleSheet()
693
+
694
+ workbox_prefs = self.uiWorkboxTAB.save_prefs()
695
+ pref['workbox_prefs'] = workbox_prefs
696
+
697
+ pref['editor_cls'] = self.editor_cls_name
698
+
699
+ self.save_prefs(pref)
700
+
701
+ def load_prefs(self):
702
+ filename = prefs.prefs_path('preditor_pref.json', core_name=self.name)
703
+ if os.path.exists(filename):
704
+ with open(filename) as fp:
705
+ return json.load(fp)
706
+ return {}
707
+
708
+ def save_prefs(self, pref):
709
+ # Save preferences to disk
710
+ filename = prefs.prefs_path('preditor_pref.json', core_name=self.name)
711
+ dirname = os.path.dirname(filename)
712
+ if not os.path.exists(dirname):
713
+ os.makedirs(dirname)
714
+ with open(filename, 'w') as fp:
715
+ json.dump(pref, fp, indent=4)
716
+
717
+ def restartLogger(self):
718
+ """Closes this PrEditor instance and starts a new process with the same
719
+ cli arguments.
720
+
721
+ Note: This only works if PrEditor is running in standalone mode. It doesn't
722
+ quit the QApplication or other host process. It simply closes this instance
723
+ of PrEditor, saving its preferences, which should allow Qt to exit if no
724
+ other windows are open.
725
+ """
726
+ self.close()
727
+
728
+ # Get the current command and launch it as a new process. This handles
729
+ # use of the preditor/preditor executable launchers.
730
+ cmd = sys.argv[0]
731
+ args = sys.argv[1:]
732
+
733
+ if os.path.basename(cmd) == "__main__.py":
734
+ # Handles using `python -m preditor` style launch.
735
+ cmd = sys.executable
736
+ args = ["-m", "preditor"] + args
737
+ QtCore.QProcess.startDetached(cmd, args)
738
+
739
+ def restorePrefs(self):
740
+ pref = self.load_prefs()
741
+
742
+ # Editor selection
743
+ self.editor_cls_name = pref.get('editor_cls')
744
+ if self.editor_cls_name:
745
+ self.editor_cls_name, editor_cls = plugins.editor(self.editor_cls_name)
746
+ self.uiWorkboxTAB.editor_cls = editor_cls
747
+ else:
748
+ self.uiWorkboxTAB.editor_cls = None
749
+ # Set the workbox core_name so it reads/writes its tabs content into the
750
+ # same core_name preference folder.
751
+ self.uiWorkboxTAB.core_name = self.name
752
+ self.uiEditorChooserWGT.set_editor_name(self.editor_cls_name)
753
+
754
+ # Geometry
755
+ if 'loggergeom' in pref:
756
+ self.setGeometry(*pref['loggergeom'])
757
+ self.uiEditorVerticalACT.setChecked(pref.get('SplitterVertical', False))
758
+ self.adjustWorkboxOrientation(self.uiEditorVerticalACT.isChecked())
759
+
760
+ sizes = pref.get('SplitterSize')
761
+ if sizes:
762
+ self.uiSplitterSPLIT.setSizes(sizes)
763
+ self.setWindowState(Qt.WindowStates(pref.get('windowState', 0)))
764
+ self.uiIndentationsTabsACT.setChecked(pref.get('tabIndent', True))
765
+ self.uiCopyTabsToSpacesACT.setChecked(pref.get('copyIndentsAsSpaces', False))
766
+ self.uiAutoCompleteEnabledACT.setChecked(pref.get('hintingEnabled', True))
767
+
768
+ # completer settings
769
+ self.setCaseSensitive(pref.get('caseSensitive', True))
770
+ completerMode = CompleterMode(pref.get('completerMode', 0))
771
+ self.cycleToCompleterMode(completerMode)
772
+ self.setCompleterMode(completerMode)
773
+
774
+ self.setSpellCheckEnabled(self.uiSpellCheckEnabledACT.isChecked())
775
+ self.uiSpellCheckEnabledACT.setChecked(pref.get('spellCheckEnabled', False))
776
+ self.uiSpellCheckEnabledACT.setDisabled(False)
777
+
778
+ self.uiConsoleTXT.completer().setEnabled(
779
+ self.uiAutoCompleteEnabledACT.isChecked()
780
+ )
781
+ self.uiAutoSaveSettingssACT.setChecked(pref.get('uiAutoSaveSettingssACT', True))
782
+
783
+ self.uiAutoPromptACT.setChecked(pref.get('uiAutoPromptACT', False))
784
+ self.uiLinesInNewWorkboxACT.setChecked(
785
+ pref.get('uiLinesInNewWorkboxACT', False)
786
+ )
787
+ self.uiErrorHyperlinksACT.setChecked(pref.get('uiErrorHyperlinksACT', True))
788
+
789
+ # External text editor filepath and command template
790
+ defaultExePath = r"C:\Program Files\Sublime Text 3\sublime_text.exe"
791
+ defaultCmd = r"{exePath} {modulePath}:{lineNum}"
792
+ self.textEditorPath = pref.get('textEditorPath', defaultExePath)
793
+ self.textEditorCmdTempl = pref.get('textEditorCmdTempl', defaultCmd)
794
+
795
+ self.uiWordWrapACT.setChecked(pref.get('wordWrap', True))
796
+ self.setWordWrap(self.uiWordWrapACT.isChecked())
797
+ self.uiClearBeforeRunningACT.setChecked(pref.get('clearBeforeRunning', False))
798
+ self.uiClearLogOnRefreshACT.setChecked(pref.get('clearBeforeEnvRefresh', False))
799
+ self.setClearBeforeRunning(self.uiClearBeforeRunningACT.isChecked())
800
+
801
+ self._stylesheet = pref.get('currentStyleSheet', 'Bright')
802
+ if self._stylesheet == 'Custom':
803
+ self.setStyleSheet(pref.get('styleSheet', ''))
804
+ else:
805
+ self.setStyleSheet(self._stylesheet)
806
+ self.uiConsoleTXT.flash_time = pref.get('flash_time', 1.0)
807
+
808
+ self.uiWorkboxTAB.restore_prefs(pref.get('workbox_prefs', {}))
809
+
810
+ # Ensure the correct workbox stack page is shown
811
+ self.update_workbox_stack()
812
+
813
+ _font = pref.get('consoleFont', None)
814
+ if _font:
815
+ font = QFont()
816
+ if font.fromString(_font):
817
+ self.console().setConsoleFont(font)
818
+
819
+ def restoreToolbars(self, pref=None):
820
+ if pref is None:
821
+ pref = self.load_prefs()
822
+
823
+ state = pref.get('toolbarStates', None)
824
+ if state:
825
+ state = QByteArray.fromHex(bytes(state, 'utf-8'))
826
+ self.restoreState(state)
827
+
828
+ def setAutoCompleteEnabled(self, state):
829
+ self.uiConsoleTXT.completer().setEnabled(state)
830
+ for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
831
+ workbox.__set_auto_complete_enabled__(state)
832
+
833
+ def setSpellCheckEnabled(self, state):
834
+ try:
835
+ self.delayable_engine.set_delayable_enabled('spell_check', state)
836
+ except KeyError:
837
+ # Spell check can not be enabled
838
+ if self.isVisible():
839
+ # Only show warning if Logger is visible and also disable the action
840
+ self.uiSpellCheckEnabledACT.setDisabled(True)
841
+ QMessageBox.warning(
842
+ self, "Spell-Check", 'Unable to activate spell check.'
843
+ )
844
+
845
+ def setStatusText(self, txt):
846
+ """Set the text shown in the menu corner of the menu bar.
847
+
848
+ Args:
849
+ txt (str): The text to show in the status text label.
850
+ """
851
+ self.uiStatusLBL.setText(txt)
852
+ self.uiMenuBar.adjustSize()
853
+
854
+ def clearStatusText(self):
855
+ """Clear any displayed status text"""
856
+ self.uiStatusLBL.setText('')
857
+ self.uiMenuBar.adjustSize()
858
+
859
+ def autoHideStatusText(self):
860
+ """Set timer to automatically clear status text"""
861
+ if self.statusTimer.isActive():
862
+ self.statusTimer.stop()
863
+ self.statusTimer.singleShot(2000, self.clearStatusText)
864
+ self.statusTimer.start()
865
+
866
+ def setStyleSheet(self, stylesheet, recordPrefs=True):
867
+ """Accepts the name of a stylesheet included with blurdev, or a full
868
+ path to any stylesheet. If given None, it will default to Bright.
869
+ """
870
+ sheet = None
871
+ if stylesheet is None:
872
+ stylesheet = 'Bright'
873
+ if os.path.isfile(stylesheet):
874
+ # A path to a stylesheet was passed in
875
+ with open(stylesheet) as f:
876
+ sheet = f.read()
877
+ self._stylesheet = stylesheet
878
+ else:
879
+ # Try to find an installed stylesheet with the given name
880
+ sheet, valid = stylesheets.read_stylesheet(stylesheet)
881
+ if valid:
882
+ self._stylesheet = stylesheet
883
+ else:
884
+ # Assume the user passed the text of the stylesheet directly
885
+ sheet = stylesheet
886
+ self._stylesheet = 'Custom'
887
+
888
+ # Load the stylesheet
889
+ if sheet is not None:
890
+ super(LoggerWindow, self).setStyleSheet(sheet)
891
+
892
+ # Update the style menu
893
+ for act in self.uiStyleMENU.actions():
894
+ name = act.objectName()
895
+ isCurrent = name == 'ui{}ACT'.format(self._stylesheet)
896
+ act.setChecked(isCurrent)
897
+
898
+ # Notify widgets that the styleSheet has changed
899
+ self.styleSheetChanged.emit(stylesheet)
900
+
901
+ def setCaseSensitive(self, state):
902
+ """Set completer case-sensivity"""
903
+ completer = self.console().completer()
904
+ completer.setCaseSensitive(state)
905
+ self.uiAutoCompleteCaseSensitiveACT.setChecked(state)
906
+ self.reportCaseChange(state)
907
+ completer.refreshList()
908
+
909
+ def toggleCaseSensitive(self):
910
+ """Toggle completer case-sensitivity"""
911
+ state = self.console().completer().caseSensitive()
912
+ self.reportCaseChange(state)
913
+ self.setCaseSensitive(not state)
914
+
915
+ # Completer Modes
916
+ def cycleCompleterMode(self):
917
+ """Cycle comleter mode"""
918
+ completerMode = next(self.completerModeCycle)
919
+ self.setCompleterMode(completerMode)
920
+ self.reportCompleterModeChange(completerMode)
921
+
922
+ def cycleToCompleterMode(self, completerMode):
923
+ """
924
+ Syncs the completerModeCycle iterator to currently chosen completerMode
925
+ Args:
926
+ completerMode: Chosen CompleterMode ENUM member
927
+ """
928
+ for _ in range(len(CompleterMode)):
929
+ tempMode = next(self.completerModeCycle)
930
+ if tempMode == completerMode:
931
+ break
932
+
933
+ def setCompleterMode(self, completerMode):
934
+ """
935
+ Set the completer mode to chosen mode
936
+ Args:
937
+ completerMode: Chosen CompleterMode ENUM member
938
+ """
939
+ completer = self.console().completer()
940
+
941
+ completer.setCompleterMode(completerMode)
942
+ completer.buildCompleter()
943
+
944
+ for action in self.uiCompleterModeMENU.actions():
945
+ action.setChecked(action.data() == completerMode)
946
+
947
+ def selectCompleterMode(self, action):
948
+ if not action.isChecked():
949
+ action.setChecked(True)
950
+ return
951
+ """
952
+ Handle when completer mode is chosen via menu
953
+ Will sync mode iterator and set the completion mode
954
+ Args:
955
+ action: the menu action associated with the chosen mode
956
+ """
957
+
958
+ # update cycleToCompleterMode to current Mode
959
+ mode = action.data()
960
+ self.cycleToCompleterMode(mode)
961
+ self.setCompleterMode(mode)
962
+
963
+ def reportCaseChange(self, state):
964
+ """Update status text with current Case Sensitivity Mode"""
965
+ text = "Case Sensitive " if state else "Case Insensitive "
966
+ self.setStatusText(text)
967
+ self.autoHideStatusText()
968
+
969
+ def reportCompleterModeChange(self, mode):
970
+ """Update status text with current Completer Mode"""
971
+ self.setStatusText('Completer Mode: {} '.format(mode.displayName()))
972
+ self.autoHideStatusText()
973
+
974
+ def setClearBeforeRunning(self, state):
975
+ self.uiRunSelectedACT.setIcon(QIcon(resourcePath('img/playlist-play.png')))
976
+ self.uiRunAllACT.setIcon(QIcon(resourcePath('img/play.png')))
977
+
978
+ def setFlashWindowInterval(self):
979
+ value = self.uiConsoleTXT.flash_time
980
+ msg = (
981
+ 'If running code in the logger takes X seconds or longer,\n'
982
+ 'the window will flash if it is not in focus.\n'
983
+ 'Setting the value to zero will disable flashing.'
984
+ )
985
+ value, success = QInputDialog.getDouble(self, 'Set flash window', msg, value)
986
+ if success:
987
+ self.uiConsoleTXT.flash_time = value
988
+
989
+ def setWordWrap(self, state):
990
+ if state:
991
+ self.uiConsoleTXT.setLineWrapMode(self.uiConsoleTXT.WidgetWidth)
992
+ else:
993
+ self.uiConsoleTXT.setLineWrapMode(self.uiConsoleTXT.NoWrap)
994
+
995
+ def show_about(self):
996
+ """Shows `preditor.about_preditor()`'s output in a message box."""
997
+ msg = about_preditor(instance=self)
998
+ QMessageBox.information(self, 'About PrEditor', '<pre>{}</pre>'.format(msg))
999
+
1000
+ def showEnvironmentVars(self):
1001
+ dlg = Dialog(self)
1002
+ lyt = QVBoxLayout(dlg)
1003
+ lbl = QTextBrowser(dlg)
1004
+ lyt.addWidget(lbl)
1005
+ dlg.setWindowTitle('Blurdev Environment Variable Help')
1006
+ with open(resourcePath('environment_variables.html')) as f:
1007
+ lbl.setText(f.read().replace('\n', ''))
1008
+ dlg.setMinimumSize(600, 400)
1009
+ dlg.show()
1010
+
1011
+ def showEvent(self, event):
1012
+ super(LoggerWindow, self).showEvent(event)
1013
+ self.restoreToolbars()
1014
+ self.updateIndentationsUseTabs()
1015
+ self.updateCopyIndentsAsSpaces()
1016
+
1017
+ # Adjust the minimum height of the label so it's text is the same as
1018
+ # the action menu text
1019
+ height = self.uiMenuBar.actionGeometry(self.uiFileMENU.menuAction()).height()
1020
+ self.uiStatusLBL.setMinimumHeight(height)
1021
+
1022
+ @Slot()
1023
+ def show_workbox_options(self):
1024
+ self.uiWorkboxSTACK.setCurrentIndex(WorkboxPages.Options)
1025
+
1026
+ @Slot()
1027
+ def show_focus_name(self):
1028
+ model = GroupTabListItemModel(manager=self.uiWorkboxTAB)
1029
+ model.process()
1030
+
1031
+ def update_tab(index):
1032
+ group, tab = model.workbox_indexes_from_model_index(index)
1033
+ if group is not None:
1034
+ self.uiWorkboxTAB.set_current_groups_from_index(group, tab)
1035
+
1036
+ w = FuzzySearch(model, parent=self)
1037
+ w.selected.connect(update_tab)
1038
+ w.canceled.connect(update_tab)
1039
+ w.highlighted.connect(update_tab)
1040
+ w.popup()
1041
+
1042
+ def updateCopyIndentsAsSpaces(self):
1043
+ for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
1044
+ workbox.__set_copy_indents_as_spaces__(
1045
+ self.uiCopyTabsToSpacesACT.isChecked()
1046
+ )
1047
+
1048
+ def updateIndentationsUseTabs(self):
1049
+ for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
1050
+ workbox.__set_indentations_use_tabs__(
1051
+ self.uiIndentationsTabsACT.isChecked()
1052
+ )
1053
+
1054
+ @Slot()
1055
+ def update_workbox_stack(self):
1056
+ if self.uiWorkboxTAB.editor_cls:
1057
+ index = WorkboxPages.Workboxes
1058
+ else:
1059
+ index = WorkboxPages.Options
1060
+
1061
+ self.uiWorkboxSTACK.setCurrentIndex(index)
1062
+
1063
+ def shutdown(self):
1064
+ # close out of the ide system
1065
+
1066
+ # if this is the global instance, then allow it to be deleted on close
1067
+ if self == LoggerWindow._instance:
1068
+ self.setAttribute(Qt.WA_DeleteOnClose, True)
1069
+ LoggerWindow._instance = None
1070
+
1071
+ # clear out the system
1072
+ self.close()
1073
+
1074
+ def nextTab(self):
1075
+ """Move focus to next workbox tab"""
1076
+ tabWidget = self.uiWorkboxTAB.currentWidget()
1077
+ if not tabWidget.currentWidget().hasFocus():
1078
+ tabWidget.currentWidget().setFocus()
1079
+
1080
+ index = tabWidget.currentIndex()
1081
+ if index == tabWidget.count() - 1:
1082
+ tabWidget.setCurrentIndex(0)
1083
+ else:
1084
+ tabWidget.setCurrentIndex(index + 1)
1085
+
1086
+ def prevTab(self):
1087
+ """Move focus to previous workbox tab"""
1088
+ tabWidget = self.uiWorkboxTAB.currentWidget()
1089
+ if not tabWidget.currentWidget().hasFocus():
1090
+ tabWidget.currentWidget().setFocus()
1091
+
1092
+ index = tabWidget.currentIndex()
1093
+ if index == 0:
1094
+ tabWidget.setCurrentIndex(tabWidget.count() - 1)
1095
+ else:
1096
+ tabWidget.setCurrentIndex(index - 1)
1097
+
1098
+ def gotoGroupByIndex(self, index):
1099
+ """Generally to be used in conjunction with the Ctrl+Alt+<num> keyboard
1100
+ shortcuts, which allow user to jump directly to another tab, mimicking
1101
+ web browser functionality.
1102
+ """
1103
+ if index == -1:
1104
+ index = self.uiWorkboxTAB.count() - 1
1105
+ else:
1106
+ count = self.uiWorkboxTAB.count()
1107
+ index = min(index, count)
1108
+ index -= 1
1109
+
1110
+ self.uiWorkboxTAB.setCurrentIndex(index)
1111
+
1112
+ def gotoTabByIndex(self, index):
1113
+ """Generally to be used in conjunction with the Ctrl+<num> keyboard
1114
+ shortcuts, which allow user to jump directly to another tab, mimicking
1115
+ web browser functionality.
1116
+ """
1117
+ group_tab = self.uiWorkboxTAB.currentWidget()
1118
+ if index == -1:
1119
+ index = group_tab.count() - 1
1120
+ else:
1121
+ count = group_tab.count()
1122
+ index = min(index, count)
1123
+ index -= 1
1124
+
1125
+ group_tab.setCurrentIndex(index)
1126
+
1127
+ @staticmethod
1128
+ def instance(
1129
+ parent=None, name=None, run_workbox=False, create=True, standalone=False
1130
+ ):
1131
+ """Returns the existing instance of the PrEditor gui creating it on first call.
1132
+
1133
+ Args:
1134
+ parent (QWidget, optional): If the instance hasn't been created yet, create
1135
+ it and parent it to this object.
1136
+ run_workbox (bool, optional): If the instance hasn't been created yet, this
1137
+ will execute the active workbox's code once fully initialized.
1138
+ create (bool, optional): Returns None if the instance has not been created.
1139
+ standalone (bool, optional): Launch PrEditor in standalone mode. This
1140
+ enables extra options that only make sense when it is running as
1141
+ its own app, not inside of another app.
1142
+
1143
+ Returns:
1144
+ Returns a fully initialized instance of the PrEditor gui. If called more
1145
+ than once, the same instance will be returned. If create is False, it may
1146
+ return None.
1147
+ """
1148
+ # create the instance for the logger
1149
+ if not LoggerWindow._instance:
1150
+ if not create:
1151
+ return None
1152
+
1153
+ # create the logger instance
1154
+ inst = LoggerWindow(
1155
+ parent, name=name, run_workbox=run_workbox, standalone=standalone
1156
+ )
1157
+
1158
+ # RV has a Unique window structure. It makes more sense to not parent a
1159
+ # singleton window than to parent it to a specific top level window.
1160
+ if core.objectName() == 'rv':
1161
+ inst.setParent(None)
1162
+ inst.setAttribute(Qt.WA_QuitOnClose, False)
1163
+
1164
+ # protect the memory
1165
+ inst.setAttribute(Qt.WA_DeleteOnClose, False)
1166
+
1167
+ # cache the instance
1168
+ LoggerWindow._instance = inst
1169
+
1170
+ return LoggerWindow._instance
1171
+
1172
+ def installLogToFile(self):
1173
+ """All stdout/stderr output is also appended to this file.
1174
+
1175
+ This uses preditor.debug.logToFile(path, useOldStd=True).
1176
+ """
1177
+ if self._logToFilePath is None:
1178
+ path = osystem.defaultLogFile()
1179
+ path, _ = QtCompat.QFileDialog.getSaveFileName(
1180
+ self, "Log Output to File", path
1181
+ )
1182
+ if not path:
1183
+ return
1184
+ path = os.path.normpath(path)
1185
+ print('Output logged to: "{}"'.format(path))
1186
+ debug.logToFile(path, useOldStd=True)
1187
+ # Store the std's so we can clear them later
1188
+ self._stds = (sys.stdout, sys.stderr)
1189
+ self.uiLogToFileACT.setText('Output Logged to File')
1190
+ self.uiLogToFileClearACT.setVisible(True)
1191
+ self._logToFilePath = path
1192
+ else:
1193
+ print('Output logged to: "{}"'.format(self._logToFilePath))
1194
+
1195
+ @classmethod
1196
+ def instance_shutdown(cls):
1197
+ """Call shutdown the LoggerWindow instance only if it was instantiated.
1198
+
1199
+ Returns:
1200
+ bool: If a shutdown was required
1201
+ """
1202
+ if cls._instance:
1203
+ cls._instance.shutdown()
1204
+ return True
1205
+ return False