pygpt-net 2.4.37__py3-none-any.whl → 2.4.42__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. CHANGELOG.md +37 -0
  2. README.md +176 -182
  3. pygpt_net/CHANGELOG.txt +37 -0
  4. pygpt_net/__init__.py +3 -3
  5. pygpt_net/app.py +3 -1
  6. pygpt_net/controller/__init__.py +7 -3
  7. pygpt_net/controller/access/control.py +1 -1
  8. pygpt_net/controller/access/voice.py +11 -5
  9. pygpt_net/controller/agent/experts.py +11 -6
  10. pygpt_net/controller/agent/legacy.py +8 -6
  11. pygpt_net/controller/agent/llama.py +4 -2
  12. pygpt_net/controller/assistant/__init__.py +9 -4
  13. pygpt_net/controller/assistant/batch.py +38 -21
  14. pygpt_net/controller/assistant/editor.py +7 -6
  15. pygpt_net/controller/assistant/files.py +23 -7
  16. pygpt_net/controller/assistant/store.py +20 -7
  17. pygpt_net/controller/assistant/threads.py +34 -8
  18. pygpt_net/controller/attachment.py +29 -10
  19. pygpt_net/controller/audio/__init__.py +26 -5
  20. pygpt_net/controller/calendar/__init__.py +23 -4
  21. pygpt_net/controller/calendar/note.py +57 -11
  22. pygpt_net/controller/camera.py +4 -12
  23. pygpt_net/controller/chat/__init__.py +5 -3
  24. pygpt_net/controller/chat/attachment.py +34 -7
  25. pygpt_net/controller/chat/audio.py +2 -2
  26. pygpt_net/controller/chat/command.py +4 -2
  27. pygpt_net/controller/chat/common.py +11 -4
  28. pygpt_net/controller/chat/files.py +10 -3
  29. pygpt_net/controller/chat/image.py +17 -5
  30. pygpt_net/controller/chat/input.py +10 -7
  31. pygpt_net/controller/chat/output.py +21 -6
  32. pygpt_net/controller/chat/render.py +100 -21
  33. pygpt_net/controller/chat/response.py +34 -7
  34. pygpt_net/controller/chat/stream.py +4 -2
  35. pygpt_net/controller/chat/text.py +6 -4
  36. pygpt_net/controller/command.py +11 -3
  37. pygpt_net/controller/config/__init__.py +34 -6
  38. pygpt_net/controller/config/field/checkbox.py +7 -4
  39. pygpt_net/controller/config/field/cmd.py +7 -5
  40. pygpt_net/controller/config/field/combo.py +14 -6
  41. pygpt_net/controller/config/field/dictionary.py +14 -11
  42. pygpt_net/controller/config/field/input.py +9 -6
  43. pygpt_net/controller/config/field/slider.py +11 -8
  44. pygpt_net/controller/config/field/textarea.py +8 -5
  45. pygpt_net/controller/config/placeholder.py +66 -21
  46. pygpt_net/controller/ctx/__init__.py +138 -49
  47. pygpt_net/controller/ctx/common.py +15 -4
  48. pygpt_net/controller/ctx/extra.py +11 -3
  49. pygpt_net/controller/ctx/summarizer.py +24 -5
  50. pygpt_net/controller/debug/__init__.py +27 -6
  51. pygpt_net/controller/dialogs/confirm.py +34 -7
  52. pygpt_net/controller/dialogs/debug.py +4 -2
  53. pygpt_net/controller/dialogs/info.py +7 -2
  54. pygpt_net/controller/files.py +48 -10
  55. pygpt_net/controller/finder.py +11 -5
  56. pygpt_net/controller/idx/__init__.py +10 -3
  57. pygpt_net/controller/idx/common.py +4 -2
  58. pygpt_net/controller/idx/indexer.py +25 -17
  59. pygpt_net/controller/idx/settings.py +9 -3
  60. pygpt_net/controller/kernel/__init__.py +34 -8
  61. pygpt_net/controller/kernel/reply.py +12 -3
  62. pygpt_net/controller/kernel/stack.py +5 -3
  63. pygpt_net/controller/lang/custom.py +2 -7
  64. pygpt_net/controller/lang/mapping.py +5 -3
  65. pygpt_net/controller/layout.py +2 -2
  66. pygpt_net/controller/mode.py +16 -4
  67. pygpt_net/controller/model/__init__.py +14 -3
  68. pygpt_net/controller/model/editor.py +8 -3
  69. pygpt_net/controller/notepad.py +26 -12
  70. pygpt_net/controller/painter/capture.py +23 -4
  71. pygpt_net/controller/painter/common.py +9 -7
  72. pygpt_net/controller/plugins/__init__.py +19 -5
  73. pygpt_net/controller/plugins/presets.py +15 -6
  74. pygpt_net/controller/plugins/settings.py +9 -3
  75. pygpt_net/controller/presets/__init__.py +55 -16
  76. pygpt_net/controller/presets/editor.py +26 -10
  77. pygpt_net/controller/settings/__init__.py +3 -2
  78. pygpt_net/controller/settings/editor.py +29 -7
  79. pygpt_net/controller/settings/profile.py +22 -5
  80. pygpt_net/controller/theme/__init__.py +54 -12
  81. pygpt_net/controller/theme/common.py +24 -2
  82. pygpt_net/controller/theme/markdown.py +32 -16
  83. pygpt_net/controller/theme/menu.py +26 -5
  84. pygpt_net/controller/theme/nodes.py +2 -5
  85. pygpt_net/controller/tools/__init__.py +40 -2
  86. pygpt_net/controller/ui/__init__.py +4 -6
  87. pygpt_net/controller/ui/mode.py +16 -21
  88. pygpt_net/controller/ui/tabs.py +363 -65
  89. pygpt_net/core/access/actions.py +6 -4
  90. pygpt_net/core/access/shortcuts.py +4 -3
  91. pygpt_net/core/access/voice.py +6 -5
  92. pygpt_net/core/agents/legacy.py +4 -2
  93. pygpt_net/core/agents/memory.py +7 -2
  94. pygpt_net/core/agents/observer/evaluation.py +15 -7
  95. pygpt_net/core/agents/provider.py +9 -4
  96. pygpt_net/core/agents/runner.py +61 -15
  97. pygpt_net/core/agents/tools.py +23 -5
  98. pygpt_net/core/assistants/__init__.py +6 -4
  99. pygpt_net/core/assistants/files.py +35 -12
  100. pygpt_net/core/assistants/store.py +20 -10
  101. pygpt_net/core/attachments/__init__.py +54 -15
  102. pygpt_net/core/attachments/context.py +92 -29
  103. pygpt_net/core/audio/__init__.py +74 -3
  104. pygpt_net/core/audio/context.py +7 -2
  105. pygpt_net/core/audio/whisper.py +37 -0
  106. pygpt_net/core/bridge/__init__.py +22 -6
  107. pygpt_net/core/bridge/context.py +5 -3
  108. pygpt_net/core/bridge/worker.py +2 -2
  109. pygpt_net/core/calendar/__init__.py +57 -11
  110. pygpt_net/core/chain/__init__.py +8 -2
  111. pygpt_net/core/chain/chat.py +10 -8
  112. pygpt_net/core/chain/completion.py +10 -7
  113. pygpt_net/core/command.py +62 -17
  114. pygpt_net/core/ctx/__init__.py +260 -58
  115. pygpt_net/core/ctx/bag.py +25 -4
  116. pygpt_net/core/ctx/container.py +28 -17
  117. pygpt_net/core/ctx/idx.py +45 -8
  118. pygpt_net/core/ctx/output.py +95 -74
  119. pygpt_net/core/ctx/reply.py +5 -2
  120. pygpt_net/core/db/__init__.py +8 -7
  121. pygpt_net/core/db/viewer.py +17 -11
  122. pygpt_net/core/debug/__init__.py +10 -9
  123. pygpt_net/core/debug/events.py +22 -10
  124. pygpt_net/core/debug/tabs.py +9 -3
  125. pygpt_net/core/docker/__init__.py +11 -5
  126. pygpt_net/core/docker/builder.py +11 -3
  127. pygpt_net/core/events/app.py +5 -3
  128. pygpt_net/core/events/base.py +11 -5
  129. pygpt_net/core/events/control.py +5 -3
  130. pygpt_net/core/events/event.py +17 -7
  131. pygpt_net/core/events/kernel.py +5 -3
  132. pygpt_net/core/events/render.py +5 -3
  133. pygpt_net/core/experts/__init__.py +5 -4
  134. pygpt_net/core/filesystem/__init__.py +52 -34
  135. pygpt_net/core/filesystem/actions.py +8 -5
  136. pygpt_net/core/filesystem/editor.py +13 -3
  137. pygpt_net/core/filesystem/types.py +12 -7
  138. pygpt_net/core/filesystem/url.py +7 -3
  139. pygpt_net/core/history.py +3 -2
  140. pygpt_net/core/idx/__init__.py +48 -27
  141. pygpt_net/core/idx/chat.py +51 -17
  142. pygpt_net/core/idx/context.py +6 -2
  143. pygpt_net/core/idx/indexing.py +107 -42
  144. pygpt_net/core/idx/llm.py +11 -3
  145. pygpt_net/core/idx/metadata.py +13 -3
  146. pygpt_net/core/idx/types/ctx.py +32 -6
  147. pygpt_net/core/idx/types/external.py +41 -7
  148. pygpt_net/core/idx/types/files.py +31 -6
  149. pygpt_net/core/image.py +15 -4
  150. pygpt_net/core/installer.py +2 -4
  151. pygpt_net/core/llm/__init__.py +13 -3
  152. pygpt_net/core/locale.py +34 -8
  153. pygpt_net/core/models.py +63 -17
  154. pygpt_net/core/modes.py +11 -13
  155. pygpt_net/core/notepad.py +10 -5
  156. pygpt_net/core/plugins.py +31 -19
  157. pygpt_net/core/presets.py +37 -17
  158. pygpt_net/core/profile.py +21 -7
  159. pygpt_net/core/prompt/__init__.py +10 -3
  160. pygpt_net/core/prompt/custom.py +7 -6
  161. pygpt_net/core/prompt/template.py +9 -3
  162. pygpt_net/core/render/base.py +117 -22
  163. pygpt_net/core/render/markdown/body.py +27 -7
  164. pygpt_net/core/render/markdown/renderer.py +119 -22
  165. pygpt_net/core/render/plain/body.py +22 -5
  166. pygpt_net/core/render/plain/renderer.py +97 -21
  167. pygpt_net/core/render/web/body.py +75 -25
  168. pygpt_net/core/render/web/parser.py +3 -1
  169. pygpt_net/core/render/web/renderer.py +313 -63
  170. pygpt_net/core/settings.py +10 -5
  171. pygpt_net/core/tabs/__init__.py +290 -103
  172. pygpt_net/core/tabs/tab.py +26 -5
  173. pygpt_net/core/tokens.py +47 -12
  174. pygpt_net/core/updater/__init__.py +20 -7
  175. pygpt_net/core/vision/analyzer.py +29 -6
  176. pygpt_net/core/{web.py → web/__init__.py} +29 -7
  177. pygpt_net/core/web/helpers.py +237 -0
  178. pygpt_net/data/config/config.json +15 -4
  179. pygpt_net/data/config/models.json +3 -3
  180. pygpt_net/data/config/modes.json +3 -3
  181. pygpt_net/data/config/settings.json +55 -10
  182. pygpt_net/data/config/settings_section.json +3 -0
  183. pygpt_net/data/css/style.light.css +1 -0
  184. pygpt_net/data/css/{web.css → web-blocks.css} +162 -133
  185. pygpt_net/data/css/{web.light.css → web-blocks.light.css} +7 -0
  186. pygpt_net/data/css/web-chatgpt.css +350 -0
  187. pygpt_net/data/css/web-chatgpt.dark.css +64 -0
  188. pygpt_net/data/css/web-chatgpt.light.css +75 -0
  189. pygpt_net/data/css/web-chatgpt_wide.css +350 -0
  190. pygpt_net/data/css/web-chatgpt_wide.dark.css +64 -0
  191. pygpt_net/data/css/web-chatgpt_wide.light.css +75 -0
  192. pygpt_net/data/icons/split_screen.svg +1 -0
  193. pygpt_net/data/locale/locale.de.ini +12 -0
  194. pygpt_net/data/locale/locale.en.ini +18 -2
  195. pygpt_net/data/locale/locale.es.ini +12 -0
  196. pygpt_net/data/locale/locale.fr.ini +12 -0
  197. pygpt_net/data/locale/locale.it.ini +12 -0
  198. pygpt_net/data/locale/locale.pl.ini +12 -0
  199. pygpt_net/data/locale/locale.uk.ini +12 -0
  200. pygpt_net/data/locale/locale.zh.ini +12 -0
  201. pygpt_net/data/locale/plugin.cmd_web.de.ini +2 -0
  202. pygpt_net/data/locale/plugin.cmd_web.en.ini +22 -10
  203. pygpt_net/data/locale/plugin.cmd_web.es.ini +2 -0
  204. pygpt_net/data/locale/plugin.cmd_web.fr.ini +2 -0
  205. pygpt_net/data/locale/plugin.cmd_web.it.ini +2 -0
  206. pygpt_net/data/locale/plugin.cmd_web.pl.ini +2 -0
  207. pygpt_net/data/locale/plugin.cmd_web.uk.ini +2 -0
  208. pygpt_net/data/locale/plugin.cmd_web.zh.ini +2 -0
  209. pygpt_net/data/locale/plugin.mailer.en.ini +21 -0
  210. pygpt_net/icons.qrc +1 -0
  211. pygpt_net/icons_rc.py +165 -136
  212. pygpt_net/item/ctx.py +58 -25
  213. pygpt_net/plugin/agent/__init__.py +7 -2
  214. pygpt_net/plugin/audio_input/simple.py +21 -5
  215. pygpt_net/plugin/audio_output/__init__.py +9 -1
  216. pygpt_net/plugin/base/config.py +4 -2
  217. pygpt_net/plugin/base/plugin.py +75 -23
  218. pygpt_net/plugin/base/worker.py +42 -11
  219. pygpt_net/plugin/cmd_code_interpreter/__init__.py +39 -37
  220. pygpt_net/plugin/cmd_code_interpreter/runner.py +25 -12
  221. pygpt_net/plugin/cmd_history/config.py +2 -2
  222. pygpt_net/plugin/cmd_web/__init__.py +48 -9
  223. pygpt_net/plugin/cmd_web/config.py +135 -41
  224. pygpt_net/plugin/cmd_web/websearch.py +74 -33
  225. pygpt_net/plugin/cmd_web/worker.py +142 -13
  226. pygpt_net/plugin/idx_llama_index/config.py +3 -3
  227. pygpt_net/plugin/mailer/__init__.py +123 -0
  228. pygpt_net/plugin/mailer/config.py +149 -0
  229. pygpt_net/plugin/mailer/runner.py +285 -0
  230. pygpt_net/plugin/mailer/worker.py +123 -0
  231. pygpt_net/provider/agents/base.py +5 -2
  232. pygpt_net/provider/agents/openai.py +4 -2
  233. pygpt_net/provider/agents/openai_assistant.py +4 -2
  234. pygpt_net/provider/agents/planner.py +4 -2
  235. pygpt_net/provider/agents/react.py +4 -2
  236. pygpt_net/provider/audio_output/openai_tts.py +5 -11
  237. pygpt_net/provider/core/assistant/base.py +5 -3
  238. pygpt_net/provider/core/assistant/json_file.py +8 -5
  239. pygpt_net/provider/core/assistant_file/base.py +4 -3
  240. pygpt_net/provider/core/assistant_file/db_sqlite/__init__.py +4 -3
  241. pygpt_net/provider/core/assistant_file/db_sqlite/storage.py +3 -2
  242. pygpt_net/provider/core/assistant_store/base.py +6 -4
  243. pygpt_net/provider/core/assistant_store/db_sqlite/__init__.py +5 -4
  244. pygpt_net/provider/core/assistant_store/db_sqlite/storage.py +5 -3
  245. pygpt_net/provider/core/attachment/base.py +5 -3
  246. pygpt_net/provider/core/attachment/json_file.py +4 -3
  247. pygpt_net/provider/core/calendar/base.py +5 -3
  248. pygpt_net/provider/core/calendar/db_sqlite/__init__.py +6 -5
  249. pygpt_net/provider/core/calendar/db_sqlite/storage.py +5 -4
  250. pygpt_net/provider/core/config/base.py +8 -6
  251. pygpt_net/provider/core/config/json_file.py +9 -7
  252. pygpt_net/provider/core/config/patch.py +43 -1
  253. pygpt_net/provider/core/ctx/base.py +30 -25
  254. pygpt_net/provider/core/ctx/db_sqlite/__init__.py +59 -34
  255. pygpt_net/provider/core/ctx/db_sqlite/storage.py +62 -30
  256. pygpt_net/provider/core/ctx/db_sqlite/utils.py +11 -9
  257. pygpt_net/provider/core/index/base.py +129 -23
  258. pygpt_net/provider/core/index/db_sqlite/__init__.py +130 -23
  259. pygpt_net/provider/core/index/db_sqlite/storage.py +130 -23
  260. pygpt_net/provider/core/index/db_sqlite/utils.py +4 -2
  261. pygpt_net/provider/core/mode/base.py +5 -3
  262. pygpt_net/provider/core/mode/json_file.py +7 -6
  263. pygpt_net/provider/core/model/base.py +6 -4
  264. pygpt_net/provider/core/model/json_file.py +9 -7
  265. pygpt_net/provider/core/notepad/base.py +5 -3
  266. pygpt_net/provider/core/notepad/db_sqlite/__init__.py +5 -4
  267. pygpt_net/provider/core/notepad/db_sqlite/storage.py +4 -3
  268. pygpt_net/provider/core/plugin_preset/base.py +4 -2
  269. pygpt_net/provider/core/plugin_preset/json_file.py +5 -3
  270. pygpt_net/provider/core/preset/base.py +6 -4
  271. pygpt_net/provider/core/preset/json_file.py +9 -9
  272. pygpt_net/provider/core/prompt/base.py +6 -3
  273. pygpt_net/provider/core/prompt/json_file.py +11 -6
  274. pygpt_net/provider/gpt/assistants.py +21 -11
  275. pygpt_net/provider/gpt/audio.py +6 -5
  276. pygpt_net/provider/gpt/chat.py +10 -7
  277. pygpt_net/provider/gpt/completion.py +11 -5
  278. pygpt_net/provider/gpt/image.py +9 -2
  279. pygpt_net/provider/gpt/store.py +53 -18
  280. pygpt_net/provider/gpt/vision.py +17 -11
  281. pygpt_net/provider/llms/anthropic.py +7 -2
  282. pygpt_net/provider/llms/azure_openai.py +26 -5
  283. pygpt_net/provider/llms/base.py +47 -9
  284. pygpt_net/provider/llms/google.py +7 -2
  285. pygpt_net/provider/llms/hugging_face.py +13 -3
  286. pygpt_net/provider/llms/hugging_face_api.py +18 -4
  287. pygpt_net/provider/llms/local.py +7 -2
  288. pygpt_net/provider/llms/ollama.py +30 -6
  289. pygpt_net/provider/llms/openai.py +32 -6
  290. pygpt_net/provider/vector_stores/__init__.py +45 -14
  291. pygpt_net/provider/vector_stores/base.py +35 -8
  292. pygpt_net/provider/vector_stores/chroma.py +13 -3
  293. pygpt_net/provider/vector_stores/ctx_attachment.py +31 -12
  294. pygpt_net/provider/vector_stores/elasticsearch.py +12 -3
  295. pygpt_net/provider/vector_stores/pinecode.py +12 -3
  296. pygpt_net/provider/vector_stores/redis.py +12 -3
  297. pygpt_net/provider/vector_stores/simple.py +12 -3
  298. pygpt_net/provider/vector_stores/temp.py +16 -4
  299. pygpt_net/provider/web/base.py +10 -3
  300. pygpt_net/provider/web/google_custom_search.py +9 -3
  301. pygpt_net/provider/web/microsoft_bing.py +9 -3
  302. pygpt_net/tools/__init__.py +20 -4
  303. pygpt_net/tools/audio_transcriber/__init__.py +4 -3
  304. pygpt_net/tools/base.py +28 -7
  305. pygpt_net/tools/code_interpreter/__init__.py +177 -77
  306. pygpt_net/tools/code_interpreter/ui/dialogs.py +21 -103
  307. pygpt_net/tools/code_interpreter/ui/widgets.py +284 -9
  308. pygpt_net/tools/html_canvas/__init__.py +81 -25
  309. pygpt_net/tools/html_canvas/ui/dialogs.py +46 -62
  310. pygpt_net/tools/html_canvas/ui/widgets.py +96 -3
  311. pygpt_net/tools/image_viewer/__init__.py +10 -4
  312. pygpt_net/tools/indexer/__init__.py +8 -7
  313. pygpt_net/tools/media_player/__init__.py +4 -3
  314. pygpt_net/tools/text_editor/__init__.py +36 -10
  315. pygpt_net/ui/base/context_menu.py +2 -2
  316. pygpt_net/ui/layout/chat/input.py +10 -18
  317. pygpt_net/ui/layout/chat/output.py +27 -45
  318. pygpt_net/ui/layout/ctx/ctx_list.py +13 -4
  319. pygpt_net/ui/layout/toolbox/footer.py +18 -2
  320. pygpt_net/ui/main.py +2 -2
  321. pygpt_net/ui/menu/audio.py +12 -1
  322. pygpt_net/ui/menu/config.py +7 -11
  323. pygpt_net/ui/menu/debug.py +11 -1
  324. pygpt_net/ui/menu/theme.py +9 -2
  325. pygpt_net/ui/widget/filesystem/explorer.py +2 -2
  326. pygpt_net/ui/widget/lists/context.py +27 -5
  327. pygpt_net/ui/widget/tabs/Input.py +2 -2
  328. pygpt_net/ui/widget/tabs/body.py +2 -1
  329. pygpt_net/ui/widget/tabs/layout.py +195 -0
  330. pygpt_net/ui/widget/tabs/output.py +218 -55
  331. pygpt_net/ui/widget/textarea/html.py +11 -1
  332. pygpt_net/ui/widget/textarea/output.py +10 -1
  333. pygpt_net/ui/widget/textarea/search_input.py +4 -1
  334. pygpt_net/ui/widget/textarea/web.py +49 -9
  335. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/METADATA +177 -183
  336. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/RECORD +340 -325
  337. /pygpt_net/data/css/{web.dark.css → web-blocks.dark.css} +0 -0
  338. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/LICENSE +0 -0
  339. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/WHEEL +0 -0
  340. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/entry_points.txt +0 -0
@@ -6,29 +6,26 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.11 04:00:00 #
9
+ # Updated Date: 2024.12.14 08:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import uuid
13
13
  from datetime import datetime
14
+ from typing import Optional, Any, Dict
14
15
 
15
16
  from PySide6.QtGui import QIcon
16
17
  from PySide6.QtWidgets import QVBoxLayout, QWidget, QLayout
17
18
 
18
- from .tab import Tab
19
19
  from pygpt_net.ui.widget.tabs.body import TabBody
20
20
  from pygpt_net.utils import trans
21
21
 
22
+ from .tab import Tab
23
+
22
24
 
23
25
  class Tabs:
24
26
 
25
- # types
26
- TAB_ADD = -1
27
- TAB_CHAT = 0
28
- TAB_NOTEPAD = 1
29
- TAB_FILES = 2
30
- TAB_TOOL_PAINTER = 3
31
- TAB_TOOL_CALENDAR = 4
27
+ # number of columns
28
+ NUM_COLS = 2
32
29
 
33
30
  def __init__(self, window=None):
34
31
  """
@@ -40,33 +37,40 @@ class Tabs:
40
37
  self.last_pid = -1
41
38
  self.pids = {} # pid: Tab data
42
39
  self.icons = {
43
- self.TAB_CHAT: ":/icons/chat.svg",
44
- self.TAB_NOTEPAD: ":/icons/paste.svg",
45
- self.TAB_FILES: ":/icons/folder_filled.svg",
46
- self.TAB_TOOL_PAINTER: ":/icons/brush.svg",
47
- self.TAB_TOOL_CALENDAR: ":/icons/calendar.svg",
40
+ Tab.TAB_CHAT: ":/icons/chat.svg",
41
+ Tab.TAB_NOTEPAD: ":/icons/paste.svg",
42
+ Tab.TAB_FILES: ":/icons/folder_filled.svg",
43
+ Tab.TAB_TOOL_PAINTER: ":/icons/brush.svg",
44
+ Tab.TAB_TOOL_CALENDAR: ":/icons/calendar.svg",
45
+ Tab.TAB_TOOL: ":/icons/build.svg",
48
46
  }
49
47
  self.titles = {
50
- self.TAB_CHAT: "output.tab.chat",
51
- self.TAB_NOTEPAD: "output.tab.notepad",
52
- self.TAB_FILES: "output.tab.files",
53
- self.TAB_TOOL_PAINTER: "output.tab.painter",
54
- self.TAB_TOOL_CALENDAR: "output.tab.calendar",
48
+ Tab.TAB_CHAT: "output.tab.chat",
49
+ Tab.TAB_NOTEPAD: "output.tab.notepad",
50
+ Tab.TAB_FILES: "output.tab.files",
51
+ Tab.TAB_TOOL_PAINTER: "output.tab.painter",
52
+ Tab.TAB_TOOL_CALENDAR: "output.tab.calendar",
53
+ Tab.TAB_TOOL: "output.tab.tool",
55
54
  }
56
55
 
57
- def get_tab_by_index(self, idx: int) -> Tab or None:
56
+ def get_tab_by_index(
57
+ self,
58
+ idx: int,
59
+ column_idx: int = 0
60
+ ) -> Optional[Tab]:
58
61
  """
59
62
  Get tab by index
60
63
 
61
64
  :param idx: Tab index
65
+ :param column_idx: Column index
62
66
  :return: Tab
63
67
  """
64
- tab = self.window.ui.tabs['output'].widget(idx)
68
+ tab = self.window.ui.layout.get_tabs_by_idx(column_idx).widget(idx)
65
69
  if tab is None:
66
70
  return None
67
71
  return tab.getOwner()
68
72
 
69
- def get_tab_by_pid(self, pid: int) -> Tab or None:
73
+ def get_tab_by_pid(self, pid: int) -> Optional[Tab]:
70
74
  """
71
75
  Get tab by PID
72
76
 
@@ -77,7 +81,7 @@ class Tabs:
77
81
  return self.pids[pid]
78
82
  return None
79
83
 
80
- def get_first_tab_by_type(self, type: int) -> Tab or None:
84
+ def get_first_tab_by_type(self, type: int) -> Optional[Tab]:
81
85
  """
82
86
  Get first tab by type
83
87
 
@@ -90,26 +94,37 @@ class Tabs:
90
94
  return tab
91
95
  return None
92
96
 
93
- def get_active_pid(self) -> int or None:
97
+ def get_active_pid(self) -> Optional[int]:
94
98
  """
95
99
  Get PID by active tab
96
100
 
97
101
  :return: PID
98
102
  """
99
- tab = self.get_tab_by_index(self.window.ui.tabs['output'].currentIndex())
103
+ current_column_idx = self.window.controller.ui.tabs.get_current_column_idx()
104
+ tabs = self.window.ui.layout.get_tabs_by_idx(current_column_idx)
105
+ tab = self.get_tab_by_index(tabs.currentIndex(), current_column_idx)
100
106
  if tab is None:
101
107
  return 0
102
108
  return tab.pid
103
109
 
104
- def add(self, type: int, title: str, icon=None, reference=None, data_id=None) -> Tab:
110
+ def add(
111
+ self,
112
+ type: int,
113
+ title: str,
114
+ icon: Optional[str] = None,
115
+ child: Optional[TabBody] = None,
116
+ data_id: Optional[int] = None,
117
+ tool_id: Optional[str] = None
118
+ ) -> Tab:
105
119
  """
106
120
  Add tab
107
121
 
108
122
  :param type: Tab type
109
123
  :param title: Tab title
110
124
  :param icon: Tab icon
111
- :param reference: Tab reference
125
+ :param child: Tab child (TabBody)
112
126
  :param data_id: Tab data ID
127
+ :param tool_id: Tool ID
113
128
  :return: Tab
114
129
  """
115
130
  self.last_pid += 1 # PID++, start from 0
@@ -120,8 +135,9 @@ class Tabs:
120
135
  tab.type = type
121
136
  tab.title = title
122
137
  tab.icon = icon
123
- tab.reference = reference
138
+ tab.child = child
124
139
  tab.data_id = data_id
140
+ tab.tool_id = tool_id
125
141
 
126
142
  if type == Tab.TAB_CHAT:
127
143
  self.add_chat(tab)
@@ -133,24 +149,34 @@ class Tabs:
133
149
  self.add_tool_painter(tab)
134
150
  elif type == Tab.TAB_TOOL_CALENDAR:
135
151
  self.add_tool_calendar(tab)
152
+ elif type == Tab.TAB_TOOL:
153
+ self.add_tool(tab)
136
154
 
137
155
  self.pids[tab.pid] = tab
138
156
  return tab
139
157
 
140
- def append(self, type: int, idx: int) -> Tab:
158
+ def append(
159
+ self,
160
+ type: int,
161
+ tool_id: str,
162
+ idx: int,
163
+ column_idx: int = 0
164
+ ) -> Tab:
141
165
  """
142
166
  Append tab to the right side of the tab with the specified index
143
167
 
144
168
  :param type: tab type
169
+ :param tool_id: tool ID
145
170
  :param idx: index of the tab to the right of which the new tab will be added
171
+ :param column_idx: index of the column in which the tab will be added
146
172
  :return: Tab
147
173
  """
148
174
  self.last_pid += 1 # PID++, start from 0
149
175
  title = ""
150
176
  icon = self.icons[type]
151
- if type == self.TAB_CHAT:
177
+ if type == Tab.TAB_CHAT:
152
178
  title = trans('output.tab.chat') + " {}".format(self.count_by_type(type) + 1)
153
- elif type == self.TAB_NOTEPAD:
179
+ elif type == Tab.TAB_NOTEPAD:
154
180
  title = trans('output.tab.notepad') + " {}".format(self.count_by_type(type) + 1)
155
181
 
156
182
  tab = Tab()
@@ -160,11 +186,15 @@ class Tabs:
160
186
  tab.title = title
161
187
  tab.icon = icon
162
188
  tab.new_idx = idx + 1 # place on right side
189
+ tab.column_idx = column_idx
190
+ tab.tool_id = tool_id
163
191
 
164
192
  if type == Tab.TAB_CHAT:
165
193
  self.add_chat(tab)
166
194
  elif type == Tab.TAB_NOTEPAD:
167
195
  self.add_notepad(tab)
196
+ elif type == Tab.TAB_TOOL:
197
+ self.add_tool(tab)
168
198
 
169
199
  self.pids[tab.pid] = tab
170
200
  self.update()
@@ -186,11 +216,20 @@ class Tabs:
186
216
  tab.type = data["type"]
187
217
  tab.title = data["title"]
188
218
  tab.data_id = data["data_id"]
189
- tab.reference = None
219
+ tab.child = None
190
220
 
191
221
  if 'tooltip' in data and data['tooltip'] is not None:
192
222
  tab.tooltip = data['tooltip']
193
223
 
224
+ if 'custom_name' in data and data['custom_name'] is not None:
225
+ tab.custom_name = data['custom_name']
226
+
227
+ if 'column_idx' in data and data['column_idx'] is not None:
228
+ tab.column_idx = data['column_idx']
229
+
230
+ if 'tool_id' in data and data['tool_id'] is not None:
231
+ tab.tool_id = data['tool_id']
232
+
194
233
  if tab.type in self.icons:
195
234
  tab.icon = self.icons[tab.type]
196
235
 
@@ -207,17 +246,24 @@ class Tabs:
207
246
  self.add_tool_painter(tab)
208
247
  elif tab.type == Tab.TAB_TOOL_CALENDAR: # calendar
209
248
  self.add_tool_calendar(tab)
249
+ elif tab.type == Tab.TAB_TOOL: # custom tools, id 100+
250
+ self.add_tool(tab)
210
251
 
211
252
  self.pids[tab.pid] = tab
212
253
  self.last_pid = self.get_max_pid()
213
254
 
214
- def remove_tab_by_idx(self, idx: int):
255
+ def remove_tab_by_idx(
256
+ self,
257
+ idx: int,
258
+ column_idx: int = 0
259
+ ):
215
260
  """
216
261
  Remove tab by index
217
262
 
218
263
  :param idx: Tab index
264
+ :param column_idx: Column index
219
265
  """
220
- tab = self.get_tab_by_index(idx)
266
+ tab = self.get_tab_by_index(idx, column_idx)
221
267
  if tab is None:
222
268
  return
223
269
  self.remove(tab.pid)
@@ -231,7 +277,8 @@ class Tabs:
231
277
  tab = self.get_tab_by_pid(pid)
232
278
  if tab is None:
233
279
  return
234
- self.window.ui.tabs['output'].removeTab(tab.idx)
280
+ column_idx = tab.column_idx
281
+ self.window.ui.layout.get_tabs_by_idx(column_idx).removeTab(tab.idx)
235
282
  del self.pids[pid]
236
283
  self.update()
237
284
 
@@ -241,16 +288,21 @@ class Tabs:
241
288
  self.remove(pid) # delete from PIDs and UI
242
289
  self.window.core.ctx.output.clear() # clear mapping
243
290
 
244
- def remove_all_by_type(self, type: int):
291
+ def remove_all_by_type(
292
+ self,
293
+ type: int,
294
+ column_idx: int = 0
295
+ ):
245
296
  """
246
297
  Remove all tabs by type
247
298
 
248
299
  :param type: Tab type
300
+ :param column_idx: Column index
249
301
  """
250
302
  for pid in list(self.pids):
251
303
  tab = self.pids[pid]
252
- if tab.type == type:
253
- if type == self.TAB_CHAT:
304
+ if tab.type == type and tab.column_idx == column_idx:
305
+ if type == Tab.TAB_CHAT:
254
306
  if self.count_by_type(type) == 1:
255
307
  continue # do not remove last chat tab
256
308
  self.remove(pid)
@@ -312,7 +364,25 @@ class Tabs:
312
364
  count += 1
313
365
  return count
314
366
 
315
- def get_order_by_idx_and_type(self, idx: int, type: int) -> int:
367
+ def get_max_data_id_by_type(self, type: int) -> int:
368
+ """
369
+ Get max data ID by type
370
+
371
+ :param type: Tab type
372
+ :return: Max data ID
373
+ """
374
+ max = 0
375
+ for pid in self.pids:
376
+ tab = self.pids[pid]
377
+ if tab.type == type and tab.data_id > max:
378
+ max = tab.data_id
379
+ return max
380
+
381
+ def get_order_by_idx_and_type(
382
+ self,
383
+ idx: int,
384
+ type: int
385
+ ) -> int:
316
386
  """
317
387
  Get the order of the tab by index and type
318
388
 
@@ -328,28 +398,46 @@ class Tabs:
328
398
  return order
329
399
  return -1 # Return -1 if the tab with the specified index and type is not found
330
400
 
331
- def get_min_idx_by_type(self, type: int) -> int:
401
+ def get_min_idx_by_type(
402
+ self,
403
+ type: int,
404
+ column_idx: int = 0
405
+ ) -> int:
332
406
  """
333
407
  Get min index by type
334
408
 
335
409
  :param type: Tab type
410
+ :param column_idx: Column index
336
411
  :return: Min index
337
412
  """
338
413
  min = 999999
339
414
  for pid in self.pids:
340
415
  tab = self.pids[pid]
341
- if tab.type == type and tab.idx < min:
416
+ if (tab.type == type
417
+ and tab.column_idx == column_idx
418
+ and tab.idx < min):
342
419
  min = tab.idx
343
420
  return min
344
421
 
345
422
  def update(self):
346
- """Update tabs data (pids) from UI"""
423
+ """Update tabs data (pids) from UI (all columns)"""
424
+ for n in range(0, self.NUM_COLS):
425
+ self.update_column(n)
426
+
427
+ def update_column(self, column_idx: int):
428
+ """
429
+ Update column data by index
430
+
431
+ :param column_idx: Column index
432
+ """
433
+ tabs = self.window.ui.layout.get_tabs_by_idx(column_idx)
347
434
  for pid in self.pids:
348
435
  tab = self.pids[pid]
349
- tab.idx = self.window.ui.tabs['output'].indexOf(tab.reference)
350
- tab.title = self.window.ui.tabs['output'].tabText(tab.idx)
351
- tab.tooltip = self.window.ui.tabs['output'].tabToolTip(tab.idx)
352
- tab.updated_at = datetime.now()
436
+ if tab.column_idx == column_idx:
437
+ tab.idx = tabs.indexOf(tab.child)
438
+ tab.title = tabs.tabText(tab.idx)
439
+ tab.tooltip = tabs.tabToolTip(tab.idx)
440
+ tab.updated_at = datetime.now()
353
441
 
354
442
  def add_chat(self, tab: Tab):
355
443
  """
@@ -357,15 +445,18 @@ class Tabs:
357
445
 
358
446
  :param tab: Tab instance
359
447
  """
360
- tab.reference = self.window.core.ctx.container.register_output(tab.pid)
448
+ column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
449
+ tabs = column.get_tabs()
450
+ tab.parent = column
451
+ tab.child = self.window.core.ctx.container.get(tab)
361
452
  if tab.new_idx is not None:
362
- tab.idx = self.window.ui.tabs['output'].insertTab(tab.new_idx, tab.reference, tab.title)
453
+ tab.idx = tabs.insertTab(tab.new_idx, tab.child, tab.title)
363
454
  else:
364
- tab.idx = self.window.ui.tabs['output'].addTab(tab.reference, tab.title)
365
- tab.reference.setOwner(tab)
366
- self.window.ui.tabs['output'].setTabIcon(tab.idx, QIcon(tab.icon))
455
+ tab.idx = tabs.addTab(tab.child, tab.title)
456
+ tab.child.setOwner(tab)
457
+ tabs.setTabIcon(tab.idx, QIcon(tab.icon))
367
458
  if tab.tooltip is not None:
368
- self.window.ui.tabs['output'].setTabToolTip(tab.idx, tab.tooltip)
459
+ tabs.setTabToolTip(tab.idx, tab.tooltip)
369
460
 
370
461
  def add_notepad(self, tab: Tab):
371
462
  """
@@ -374,18 +465,22 @@ class Tabs:
374
465
  :param tab: Tab instance
375
466
  """
376
467
  idx = None
468
+ column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
469
+ tabs = column.get_tabs()
470
+ tab.parent = column
471
+ tab.parent = tabs.get_column()
377
472
  if tab.data_id is not None:
378
473
  idx = tab.data_id # restore prev idx
379
- tab.reference, idx = self.window.controller.notepad.create(idx)
380
- tab.data_id = idx # notepad idx in db, enumerated from 1
474
+ tab.child, idx, data_id = self.window.controller.notepad.create(idx)
475
+ tab.data_id = data_id # notepad idx in db, enumerated from 1
381
476
  if tab.new_idx is not None:
382
- tab.idx = self.window.ui.tabs['output'].insertTab(tab.new_idx, tab.reference, tab.title)
477
+ tab.idx = tabs.insertTab(tab.new_idx, tab.child, tab.title)
383
478
  else:
384
- tab.idx = self.window.ui.tabs['output'].addTab(tab.reference, tab.title)
385
- tab.reference.setOwner(tab)
386
- self.window.ui.tabs['output'].setTabIcon(tab.idx, QIcon(tab.icon))
479
+ tab.idx = tabs.addTab(tab.child, tab.title)
480
+ tab.child.setOwner(tab)
481
+ tabs.setTabIcon(tab.idx, QIcon(tab.icon))
387
482
  if tab.tooltip is not None:
388
- self.window.ui.tabs['output'].setTabToolTip(tab.idx, tab.tooltip)
483
+ tabs.setTabToolTip(tab.idx, tab.tooltip)
389
484
 
390
485
  def add_tool_explorer(self, tab: Tab):
391
486
  """
@@ -393,12 +488,15 @@ class Tabs:
393
488
 
394
489
  :param tab: Tab instance
395
490
  """
396
- tab.reference = self.window.ui.chat.output.explorer.setup()
397
- tab.idx = self.window.ui.tabs['output'].addTab(tab.reference, tab.title)
398
- tab.reference.setOwner(tab)
399
- self.window.ui.tabs['output'].setTabIcon(tab.idx, QIcon(tab.icon))
491
+ column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
492
+ tabs = column.get_tabs()
493
+ tab.parent = column
494
+ tab.child = self.window.ui.chat.output.explorer.setup()
495
+ tab.idx = tabs.addTab(tab.child, tab.title)
496
+ tab.child.setOwner(tab)
497
+ tabs.setTabIcon(tab.idx, QIcon(tab.icon))
400
498
  if tab.tooltip is not None:
401
- self.window.ui.tabs['output'].setTabToolTip(tab.idx, tab.tooltip)
499
+ tabs.setTabToolTip(tab.idx, tab.tooltip)
402
500
 
403
501
  def add_tool_painter(self, tab: Tab):
404
502
  """
@@ -406,12 +504,15 @@ class Tabs:
406
504
 
407
505
  :param tab: Tab instance
408
506
  """
409
- tab.reference = self.window.ui.chat.output.painter.setup()
410
- tab.idx = self.window.ui.tabs['output'].addTab(tab.reference, tab.title)
411
- tab.reference.setOwner(tab)
412
- self.window.ui.tabs['output'].setTabIcon(tab.idx, QIcon(tab.icon))
507
+ column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
508
+ tabs = column.get_tabs()
509
+ tab.parent = column
510
+ tab.child = self.window.ui.chat.output.painter.setup()
511
+ tab.idx = tabs.addTab(tab.child, tab.title)
512
+ tab.child.setOwner(tab)
513
+ tabs.setTabIcon(tab.idx, QIcon(tab.icon))
413
514
  if tab.tooltip is not None:
414
- self.window.ui.tabs['output'].setTabToolTip(tab.idx, tab.tooltip)
515
+ tabs.setTabToolTip(tab.idx, tab.tooltip)
415
516
 
416
517
  def add_tool_calendar(self, tab: Tab):
417
518
  """
@@ -419,14 +520,63 @@ class Tabs:
419
520
 
420
521
  :param tab: Tab instance
421
522
  """
422
- tab.reference = self.window.ui.chat.output.calendar.setup()
423
- tab.idx = self.window.ui.tabs['output'].addTab(tab.reference, tab.title)
424
- tab.reference.setOwner(tab)
425
- self.window.ui.tabs['output'].setTabIcon(tab.idx, QIcon(tab.icon))
523
+ column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
524
+ tabs = column.get_tabs()
525
+ tab.parent = column
526
+ tab.child = self.window.ui.chat.output.calendar.setup()
527
+ tab.idx = tabs.addTab(tab.child, tab.title)
528
+ tab.child.setOwner(tab)
529
+ tabs.setTabIcon(tab.idx, QIcon(tab.icon))
426
530
  if tab.tooltip is not None:
427
- self.window.ui.tabs['output'].setTabToolTip(tab.idx, tab.tooltip)
531
+ tabs.setTabToolTip(tab.idx, tab.tooltip)
532
+
533
+ def add_tool(self, tab: Tab):
534
+ """
535
+ Add custom tool tab
428
536
 
429
- def get_first_by_type(self, type: int) -> Tab or None:
537
+ :param tab: Tab instance
538
+ """
539
+ column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
540
+ tabs = column.get_tabs()
541
+ tool = self.window.tools.get(tab.tool_id)
542
+ if tool is None:
543
+ raise Exception("Tool not found: {}".format(tab.tool_id))
544
+ widget = tool.as_tab(tab)
545
+ if widget is None:
546
+ raise Exception("Tool widget not found: {}".format(tab.tool_id))
547
+ tab.icon = tool.tab_icon
548
+ tab.title = trans(tool.tab_title)
549
+ tab.parent = column
550
+ tab.child = self.from_widget(widget)
551
+ tab.idx = tabs.addTab(tab.child, tab.title)
552
+ tab.child.setOwner(tab)
553
+ tabs.setTabIcon(tab.idx, QIcon(tab.icon))
554
+ if tab.tooltip is not None:
555
+ tabs.setTabToolTip(tab.idx, tab.tooltip)
556
+
557
+ def move_tab(self, tab: Tab, column_idx: int):
558
+ """
559
+ Move tab to column
560
+
561
+ :param tab: Tab instance
562
+ :param column_idx: Column index
563
+ """
564
+ if tab is None:
565
+ return
566
+ if tab.column_idx == column_idx:
567
+ return
568
+
569
+ old_column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
570
+ old_tabs = old_column.get_tabs()
571
+ old_tabs.removeTab(tab.idx)
572
+ new_column = self.window.ui.layout.get_column_by_idx(column_idx)
573
+ new_tabs = new_column.get_tabs()
574
+ tab.idx = new_tabs.addTab(tab.child, QIcon(tab.icon), tab.title)
575
+ tab.parent = new_column
576
+ tab.column_idx = column_idx
577
+ self.update()
578
+
579
+ def get_first_by_type(self, type: int) -> Optional[Tab]:
430
580
  """
431
581
  Get first tab by type
432
582
 
@@ -439,7 +589,7 @@ class Tabs:
439
589
  return tab
440
590
  return None
441
591
 
442
- def from_defaults(self) -> dict:
592
+ def from_defaults(self) -> Dict[int, Any]:
443
593
  """
444
594
  Prepare default tabs data
445
595
 
@@ -450,37 +600,44 @@ class Tabs:
450
600
  "uuid": uuid.uuid4(),
451
601
  "pid": 0,
452
602
  "idx": 0,
453
- "type": self.TAB_CHAT,
603
+ "type": Tab.TAB_CHAT,
454
604
  "data_id": None,
455
605
  "title": "Chat",
456
606
  "tooltip": "Chat",
607
+ "column_idx": 0,
457
608
  }
458
609
  data[1] = {
459
610
  "uuid": uuid.uuid4(),
460
611
  "pid": 1,
461
612
  "idx": 1,
462
- "type": self.TAB_FILES,
613
+ "type": Tab.TAB_FILES,
463
614
  "data_id": None,
464
615
  "title": "Files",
465
616
  "tooltip": "Files",
617
+ "column_idx": 0,
618
+ "tool_id": "explorer",
466
619
  }
467
620
  data[2] = {
468
621
  "uuid": uuid.uuid4(),
469
622
  "pid": 2,
470
623
  "idx": 2,
471
- "type": self.TAB_TOOL_CALENDAR,
624
+ "type": Tab.TAB_TOOL_CALENDAR,
472
625
  "data_id": None,
473
626
  "title": "Calendar",
474
627
  "tooltip": "Calendar",
628
+ "column_idx": 0,
629
+ "tool_id": "calendar",
475
630
  }
476
631
  data[3] = {
477
632
  "uuid": uuid.uuid4(),
478
633
  "pid": 3,
479
634
  "idx": 3,
480
- "type": self.TAB_TOOL_PAINTER,
635
+ "type": Tab.TAB_TOOL_PAINTER,
481
636
  "data_id": None,
482
637
  "title": "Painter",
483
638
  "tooltip": "Painter",
639
+ "column_idx": 0,
640
+ "tool_id": "painter",
484
641
  }
485
642
  """
486
643
  data[4] = {
@@ -491,6 +648,8 @@ class Tabs:
491
648
  "data_id": 1,
492
649
  "title": "Notepad",
493
650
  "tooltip": "Notepad",
651
+ "column_idx": 0,
652
+ "tool_id": "notepad",
494
653
  }
495
654
  """
496
655
  # load notepads from db
@@ -503,10 +662,12 @@ class Tabs:
503
662
  "uuid": uuid.uuid4(),
504
663
  "pid": next_idx,
505
664
  "idx": next_idx,
506
- "type": self.TAB_NOTEPAD,
665
+ "type": Tab.TAB_NOTEPAD,
507
666
  "data_id": item['data_id'],
508
667
  "title": item['title'],
509
668
  "tooltip": item['title'],
669
+ "column_idx": 0,
670
+ "tool_id": "notepad",
510
671
  }
511
672
  next_idx += 1
512
673
  return data
@@ -536,6 +697,8 @@ class Tabs:
536
697
  "title": trans(self.titles[type]),
537
698
  "tooltip": trans(self.titles[type]),
538
699
  "custom_name": False,
700
+ "column_idx": 0,
701
+ "tool_id": None,
539
702
  }
540
703
  tmp_pid += 1
541
704
 
@@ -564,11 +727,23 @@ class Tabs:
564
727
  "title": tab.title,
565
728
  "tooltip": tab.tooltip,
566
729
  "custom_name": tab.custom_name,
730
+ "column_idx": tab.column_idx,
731
+ "tool_id": tab.tool_id,
567
732
  }
733
+ opened_tabs = {}
734
+ for column_idx in range(0, self.NUM_COLS):
735
+ tabs = self.window.ui.layout.get_tabs_by_idx(column_idx)
736
+ opened_tabs[column_idx] = tabs.currentIndex()
737
+ self.window.core.config.set("tabs.opened", opened_tabs)
568
738
  self.window.core.config.set("tabs.data", data)
569
739
  self.window.core.config.save()
570
740
 
571
- def update_title(self, idx: int, title: str, tooltip: str = None):
741
+ def update_title(
742
+ self,
743
+ idx: int,
744
+ title: str,
745
+ tooltip: Optional[str] = None
746
+ ):
572
747
  """
573
748
  Update tab title
574
749
 
@@ -576,37 +751,49 @@ class Tabs:
576
751
  :param title: Tab title
577
752
  :param tooltip: Tab tooltip
578
753
  """
579
- tab = self.get_tab_by_index(idx)
754
+ column_idx = self.window.controller.ui.tabs.get_current_column_idx()
755
+ tabs = self.window.ui.layout.get_tabs_by_idx(column_idx)
756
+ tab = self.get_tab_by_index(idx, column_idx)
580
757
  if tab is None:
581
758
  return
582
759
  tab.title = title
583
760
  tab.tooltip = tooltip
584
761
  tab.custom_name = True
585
762
  if title is not None:
586
- self.window.ui.tabs['output'].setTabText(idx, title)
763
+ tabs.setTabText(idx, title)
587
764
  if tooltip is not None:
588
- self.window.ui.tabs['output'].setTabToolTip(idx, tooltip)
765
+ tabs.setTabToolTip(idx, tooltip)
589
766
 
590
767
  def reload_titles(self):
591
768
  """Reload default tab titles"""
592
- counters = {
593
- self.TAB_CHAT: 1,
594
- self.TAB_NOTEPAD: 1,
595
- self.TAB_FILES: 1,
596
- self.TAB_TOOL_PAINTER: 1,
597
- self.TAB_TOOL_CALENDAR: 1,
598
- }
599
- for pid in self.pids:
600
- tab = self.pids[pid]
601
- if tab.custom_name:
602
- continue # leave custom names
603
- tab.title = trans(self.titles[tab.type])
604
- num_tabs = self.count_by_type(tab.type)
605
- if num_tabs > 1:
606
- tab.title += " {}".format(counters[tab.type])
607
- counters[tab.type] += 1
608
- self.window.ui.tabs['output'].setTabText(tab.idx, tab.title)
609
- self.window.ui.tabs['output'].setTabToolTip(tab.idx, tab.title)
769
+ return
770
+ processed = []
771
+ for column_idx in range(0, self.NUM_COLS):
772
+ tabs = self.window.ui.layout.get_tabs_by_idx(column_idx)
773
+ counters = {
774
+ Tab.TAB_CHAT: 1,
775
+ Tab.TAB_NOTEPAD: 1,
776
+ Tab.TAB_FILES: 1,
777
+ Tab.TAB_TOOL_PAINTER: 1,
778
+ Tab.TAB_TOOL_CALENDAR: 1,
779
+ }
780
+ for pid in self.pids:
781
+ tab = self.pids[pid]
782
+ if tab.pid in processed:
783
+ continue
784
+ if tab.custom_name or tab.type == Tab.TAB_TOOL:
785
+ continue # leave custom names and tools
786
+ tab.title = trans(self.titles[tab.type])
787
+ num_tabs = self.count_by_type(tab.type)
788
+ if num_tabs > 1:
789
+ if tab.type in counters:
790
+ tab.title += " {}".format(counters[tab.type])
791
+ else:
792
+ counters[tab.type] = 1
793
+ counters[tab.type] += 1
794
+ tabs.setTabText(tab.idx, tab.title)
795
+ tabs.setTabToolTip(tab.idx, tab.title)
796
+ processed.append(pid)
610
797
 
611
798
  def from_widget(self, widget: QWidget) -> TabBody:
612
799
  """