pygpt-net 2.6.2__py3-none-any.whl → 2.6.4__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.
- pygpt_net/CHANGELOG.txt +10 -0
- pygpt_net/__init__.py +1 -1
- pygpt_net/config.py +55 -65
- pygpt_net/controller/calendar/note.py +101 -126
- pygpt_net/controller/chat/chat.py +38 -35
- pygpt_net/controller/chat/render.py +154 -214
- pygpt_net/controller/chat/stream.py +51 -25
- pygpt_net/controller/config/config.py +39 -42
- pygpt_net/controller/config/field/checkbox.py +16 -12
- pygpt_net/controller/config/field/checkbox_list.py +36 -31
- pygpt_net/controller/config/field/cmd.py +51 -57
- pygpt_net/controller/config/field/combo.py +33 -16
- pygpt_net/controller/config/field/dictionary.py +48 -55
- pygpt_net/controller/config/field/input.py +50 -32
- pygpt_net/controller/config/field/slider.py +40 -45
- pygpt_net/controller/config/field/textarea.py +20 -6
- pygpt_net/controller/config/placeholder.py +110 -231
- pygpt_net/controller/ctx/common.py +3 -2
- pygpt_net/controller/ctx/ctx.py +68 -129
- pygpt_net/controller/lang/mapping.py +57 -95
- pygpt_net/controller/lang/plugins.py +64 -55
- pygpt_net/controller/lang/settings.py +39 -38
- pygpt_net/controller/layout/layout.py +176 -109
- pygpt_net/controller/mode/mode.py +88 -85
- pygpt_net/controller/model/model.py +73 -73
- pygpt_net/controller/plugins/plugins.py +201 -240
- pygpt_net/controller/plugins/presets.py +54 -55
- pygpt_net/controller/plugins/settings.py +54 -69
- pygpt_net/controller/presets/presets.py +292 -297
- pygpt_net/controller/theme/theme.py +72 -81
- pygpt_net/controller/ui/mode.py +110 -129
- pygpt_net/controller/ui/tabs.py +69 -90
- pygpt_net/controller/ui/ui.py +47 -56
- pygpt_net/controller/ui/vision.py +24 -23
- pygpt_net/core/bridge/bridge.py +5 -5
- pygpt_net/core/command/command.py +149 -219
- pygpt_net/core/ctx/ctx.py +94 -146
- pygpt_net/core/debug/debug.py +48 -58
- pygpt_net/core/models/models.py +74 -112
- pygpt_net/core/modes/modes.py +13 -21
- pygpt_net/core/plugins/plugins.py +154 -177
- pygpt_net/core/presets/presets.py +103 -176
- pygpt_net/core/render/web/body.py +183 -193
- pygpt_net/core/render/web/renderer.py +331 -449
- pygpt_net/core/text/utils.py +28 -44
- pygpt_net/core/tokens/tokens.py +104 -203
- pygpt_net/data/config/config.json +2 -2
- pygpt_net/data/config/models.json +2 -2
- pygpt_net/item/ctx.py +141 -139
- pygpt_net/plugin/agent/plugin.py +2 -1
- pygpt_net/plugin/audio_output/plugin.py +5 -2
- pygpt_net/plugin/base/plugin.py +77 -93
- pygpt_net/plugin/bitbucket/plugin.py +3 -2
- pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
- pygpt_net/plugin/cmd_custom/plugin.py +3 -2
- pygpt_net/plugin/cmd_files/plugin.py +3 -2
- pygpt_net/plugin/cmd_history/plugin.py +3 -2
- pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
- pygpt_net/plugin/cmd_serial/plugin.py +3 -2
- pygpt_net/plugin/cmd_system/plugin.py +3 -6
- pygpt_net/plugin/cmd_web/plugin.py +3 -2
- pygpt_net/plugin/experts/plugin.py +2 -2
- pygpt_net/plugin/facebook/plugin.py +3 -4
- pygpt_net/plugin/github/plugin.py +4 -2
- pygpt_net/plugin/google/plugin.py +3 -3
- pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
- pygpt_net/plugin/mailer/plugin.py +3 -5
- pygpt_net/plugin/openai_vision/plugin.py +3 -2
- pygpt_net/plugin/real_time/plugin.py +52 -60
- pygpt_net/plugin/slack/plugin.py +3 -4
- pygpt_net/plugin/telegram/plugin.py +3 -4
- pygpt_net/plugin/twitter/plugin.py +3 -4
- pygpt_net/ui/base/config_dialog.py +83 -101
- pygpt_net/ui/base/context_menu.py +48 -46
- pygpt_net/ui/layout/toolbox/presets.py +41 -41
- pygpt_net/ui/widget/calendar/select.py +86 -70
- pygpt_net/ui/widget/lists/attachment.py +86 -44
- pygpt_net/ui/widget/lists/base_list_combo.py +85 -33
- pygpt_net/ui/widget/lists/context.py +135 -188
- pygpt_net/ui/widget/lists/preset.py +59 -61
- pygpt_net/ui/widget/textarea/web.py +18 -14
- {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/METADATA +13 -3
- {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/RECORD +86 -86
- {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
2.6.4 (2025-08-15)
|
|
2
|
+
|
|
3
|
+
- Fix: tool execution in OpenAI Agents.
|
|
4
|
+
- Optimizations.
|
|
5
|
+
|
|
6
|
+
2.6.3 (2025-08-15)
|
|
7
|
+
|
|
8
|
+
- Optimized streaming and CPU usage.
|
|
9
|
+
- Fixed crash on set label color and ctx duplicate.
|
|
10
|
+
|
|
1
11
|
2.6.2 (2025-08-15)
|
|
2
12
|
|
|
3
13
|
- Added plugins (beta): Google, Facebook, X/Twitter, Telegram, Slack, GitHub, Bitbucket.
|
pygpt_net/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ __author__ = "Marcin Szczygliński"
|
|
|
13
13
|
__copyright__ = "Copyright 2025, Marcin Szczygliński"
|
|
14
14
|
__credits__ = ["Marcin Szczygliński"]
|
|
15
15
|
__license__ = "MIT"
|
|
16
|
-
__version__ = "2.6.
|
|
16
|
+
__version__ = "2.6.4"
|
|
17
17
|
__build__ = "2025-08-15"
|
|
18
18
|
__maintainer__ = "Marcin Szczygliński"
|
|
19
19
|
__github__ = "https://github.com/szczyglis-dev/py-gpt"
|
pygpt_net/config.py
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.08.15 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
@@ -16,11 +16,15 @@ import re
|
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from packaging.version import Version
|
|
19
|
+
from operator import itemgetter
|
|
19
20
|
|
|
20
21
|
from pygpt_net.core.profile import Profile
|
|
21
22
|
from pygpt_net.provider.core.config.json_file import JsonFileProvider
|
|
22
23
|
from pygpt_net.core.types.console import Color
|
|
23
24
|
|
|
25
|
+
_RE_VERSION = re.compile(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]')
|
|
26
|
+
_RE_BUILD = re.compile(r'__build__\s*=\s*[\'"]([^\'"]*)[\'"]')
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
class Config:
|
|
26
30
|
CONFIG_DIR = 'pygpt-net'
|
|
@@ -46,9 +50,9 @@ class Config:
|
|
|
46
50
|
self.initialized_base = False
|
|
47
51
|
self.initialized_workdir = False
|
|
48
52
|
self.db_echo = False
|
|
49
|
-
self.data = {}
|
|
50
|
-
self.data_base = {}
|
|
51
|
-
self.data_session = {}
|
|
53
|
+
self.data = {}
|
|
54
|
+
self.data_base = {}
|
|
55
|
+
self.data_session = {}
|
|
52
56
|
self.version = version
|
|
53
57
|
self.dirs = {
|
|
54
58
|
"capture": "capture",
|
|
@@ -63,6 +67,9 @@ class Config:
|
|
|
63
67
|
"upload": "upload",
|
|
64
68
|
"tmp": "tmp",
|
|
65
69
|
}
|
|
70
|
+
self._app_path = None
|
|
71
|
+
self._version_cache = version if version else None
|
|
72
|
+
self._build_cache = None
|
|
66
73
|
self.provider = JsonFileProvider(window)
|
|
67
74
|
self.provider.path_app = self.get_app_path()
|
|
68
75
|
self.provider.meta = self.append_meta()
|
|
@@ -81,10 +88,8 @@ class Config:
|
|
|
81
88
|
|
|
82
89
|
def install(self):
|
|
83
90
|
"""Install database and provider data"""
|
|
84
|
-
self.window.core.db.echo = self.db_echo
|
|
91
|
+
self.window.core.db.echo = self.db_echo
|
|
85
92
|
self.window.core.db.init()
|
|
86
|
-
|
|
87
|
-
# install provider configs
|
|
88
93
|
self.provider.install()
|
|
89
94
|
|
|
90
95
|
def get_path(self) -> str:
|
|
@@ -108,7 +113,6 @@ class Config:
|
|
|
108
113
|
path = os.path.join(Path.home(), '.config', Config.CONFIG_DIR)
|
|
109
114
|
if "PYGPT_WORKDIR" in os.environ and os.environ["PYGPT_WORKDIR"] != "":
|
|
110
115
|
print("FORCE using workdir: {}".format(os.environ["PYGPT_WORKDIR"]))
|
|
111
|
-
# convert relative path to absolute path if needed
|
|
112
116
|
if not os.path.isabs(os.environ["PYGPT_WORKDIR"]):
|
|
113
117
|
path = os.path.join(os.getcwd(), os.environ["PYGPT_WORKDIR"])
|
|
114
118
|
else:
|
|
@@ -128,11 +132,11 @@ class Config:
|
|
|
128
132
|
"""
|
|
129
133
|
is_test = os.environ.get('ENV_TEST') == '1'
|
|
130
134
|
path = Path(Config.get_base_workdir())
|
|
131
|
-
if not path.exists() and not is_test:
|
|
135
|
+
if not path.exists() and not is_test:
|
|
132
136
|
path.mkdir(parents=True, exist_ok=True)
|
|
133
137
|
path_file = "path.cfg"
|
|
134
138
|
p = os.path.join(str(path), path_file)
|
|
135
|
-
if not os.path.exists(p) and not is_test:
|
|
139
|
+
if not os.path.exists(p) and not is_test:
|
|
136
140
|
with open(p, 'w', encoding='utf-8') as f:
|
|
137
141
|
f.write("")
|
|
138
142
|
else:
|
|
@@ -155,6 +159,7 @@ class Config:
|
|
|
155
159
|
"""
|
|
156
160
|
self.path = path
|
|
157
161
|
self.provider.path = path
|
|
162
|
+
self.initialized_workdir = True
|
|
158
163
|
if reload:
|
|
159
164
|
self.initialized = False
|
|
160
165
|
self.init(True)
|
|
@@ -178,7 +183,7 @@ class Config:
|
|
|
178
183
|
if dir not in self.dirs:
|
|
179
184
|
raise Exception('Unknown dir: {}'.format(dir))
|
|
180
185
|
|
|
181
|
-
dir_data_allowed =
|
|
186
|
+
dir_data_allowed = ("img", "capture", "upload")
|
|
182
187
|
|
|
183
188
|
if self.has("upload.data_dir") \
|
|
184
189
|
and self.get("upload.data_dir") \
|
|
@@ -186,8 +191,7 @@ class Config:
|
|
|
186
191
|
path = os.path.join(self.get_user_path(), self.dirs["data"], self.dirs[dir])
|
|
187
192
|
else:
|
|
188
193
|
path = os.path.join(self.get_user_path(), self.dirs[dir])
|
|
189
|
-
|
|
190
|
-
os.makedirs(path, exist_ok=True)
|
|
194
|
+
os.makedirs(path, exist_ok=True)
|
|
191
195
|
|
|
192
196
|
return path
|
|
193
197
|
|
|
@@ -208,10 +212,13 @@ class Config:
|
|
|
208
212
|
|
|
209
213
|
:return: app root path
|
|
210
214
|
"""
|
|
211
|
-
if self
|
|
212
|
-
return
|
|
215
|
+
if hasattr(self, '_app_path') and self._app_path is not None:
|
|
216
|
+
return self._app_path
|
|
217
|
+
if self.is_compiled():
|
|
218
|
+
self._app_path = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
|
213
219
|
else:
|
|
214
|
-
|
|
220
|
+
self._app_path = os.path.abspath(os.path.dirname(__file__))
|
|
221
|
+
return self._app_path
|
|
215
222
|
|
|
216
223
|
def get_user_path(self) -> str:
|
|
217
224
|
"""
|
|
@@ -229,15 +236,14 @@ class Config:
|
|
|
229
236
|
"""
|
|
230
237
|
if not self.initialized:
|
|
231
238
|
|
|
232
|
-
# if app initialization
|
|
233
239
|
if all:
|
|
234
240
|
v = self.get_version()
|
|
235
241
|
build = self.get_build().replace('.', '-')
|
|
236
|
-
|
|
242
|
+
os_name = self.window.core.platforms.get_os()
|
|
237
243
|
architecture = self.window.core.platforms.get_architecture()
|
|
238
244
|
|
|
239
245
|
print("===================================================")
|
|
240
|
-
print(f" {Color.BOLD}PyGPT {v}{Color.ENDC} build {build} ({
|
|
246
|
+
print(f" {Color.BOLD}PyGPT {v}{Color.ENDC} build {build} ({os_name}, {architecture})")
|
|
241
247
|
print(" Author: Marcin Szczyglinski")
|
|
242
248
|
print(" GitHub: https://github.com/szczyglis-dev/py-gpt")
|
|
243
249
|
print(" Website: https://pygpt.net")
|
|
@@ -246,7 +252,6 @@ class Config:
|
|
|
246
252
|
print("")
|
|
247
253
|
print(f"{Color.BOLD}Initializing...{Color.ENDC}")
|
|
248
254
|
|
|
249
|
-
# prepare and install
|
|
250
255
|
self.window.core.installer.install()
|
|
251
256
|
|
|
252
257
|
self.load(all)
|
|
@@ -258,12 +263,15 @@ class Config:
|
|
|
258
263
|
|
|
259
264
|
:return: version string
|
|
260
265
|
"""
|
|
266
|
+
if hasattr(self, '_version_cache') and self._version_cache is not None:
|
|
267
|
+
return self._version_cache
|
|
261
268
|
path = os.path.abspath(os.path.join(self.get_app_path(), '__init__.py'))
|
|
262
269
|
try:
|
|
263
270
|
with open(path, 'r', encoding="utf-8") as f:
|
|
264
271
|
data = f.read()
|
|
265
|
-
result =
|
|
266
|
-
|
|
272
|
+
result = _RE_VERSION.search(data)
|
|
273
|
+
self._version_cache = result.group(1)
|
|
274
|
+
return self._version_cache
|
|
267
275
|
except Exception as e:
|
|
268
276
|
if self.window is not None:
|
|
269
277
|
self.window.core.debug.log(e)
|
|
@@ -276,12 +284,15 @@ class Config:
|
|
|
276
284
|
|
|
277
285
|
:return: build string
|
|
278
286
|
"""
|
|
287
|
+
if self._build_cache is not None:
|
|
288
|
+
return self._build_cache
|
|
279
289
|
path = os.path.abspath(os.path.join(self.get_app_path(), '__init__.py'))
|
|
280
290
|
try:
|
|
281
291
|
with open(path, 'r', encoding="utf-8") as f:
|
|
282
292
|
data = f.read()
|
|
283
|
-
result =
|
|
284
|
-
|
|
293
|
+
result = _RE_BUILD.search(data)
|
|
294
|
+
self._build_cache = result.group(1)
|
|
295
|
+
return self._build_cache
|
|
285
296
|
except Exception as e:
|
|
286
297
|
if self.window is not None:
|
|
287
298
|
self.window.core.debug.log(e)
|
|
@@ -312,9 +323,7 @@ class Config:
|
|
|
312
323
|
:param default: default value
|
|
313
324
|
:return: value
|
|
314
325
|
"""
|
|
315
|
-
|
|
316
|
-
return self.data[key]
|
|
317
|
-
return default
|
|
326
|
+
return self.data.get(key, default)
|
|
318
327
|
|
|
319
328
|
def get_session(self, key: str, default: any = None) -> any:
|
|
320
329
|
"""
|
|
@@ -324,9 +333,7 @@ class Config:
|
|
|
324
333
|
:param default: default value
|
|
325
334
|
:return: value
|
|
326
335
|
"""
|
|
327
|
-
|
|
328
|
-
return self.data_session[key]
|
|
329
|
-
return default
|
|
336
|
+
return self.data_session.get(key, default)
|
|
330
337
|
|
|
331
338
|
def get_lang(self) -> str:
|
|
332
339
|
"""
|
|
@@ -334,7 +341,7 @@ class Config:
|
|
|
334
341
|
|
|
335
342
|
:return: language code
|
|
336
343
|
"""
|
|
337
|
-
test_lang = os.environ.get('TEST_LANGUAGE')
|
|
344
|
+
test_lang = os.environ.get('TEST_LANGUAGE')
|
|
338
345
|
if test_lang:
|
|
339
346
|
return test_lang
|
|
340
347
|
return self.get('lang', 'en')
|
|
@@ -364,12 +371,7 @@ class Config:
|
|
|
364
371
|
:param key: key
|
|
365
372
|
:return: True if exists
|
|
366
373
|
"""
|
|
367
|
-
|
|
368
|
-
return False
|
|
369
|
-
|
|
370
|
-
if key in self.data:
|
|
371
|
-
return True
|
|
372
|
-
return False
|
|
374
|
+
return key in self.data
|
|
373
375
|
|
|
374
376
|
def has_session(self, key: str) -> bool:
|
|
375
377
|
"""
|
|
@@ -378,9 +380,7 @@ class Config:
|
|
|
378
380
|
:param key: key
|
|
379
381
|
:return: True if exists
|
|
380
382
|
"""
|
|
381
|
-
|
|
382
|
-
return True
|
|
383
|
-
return False
|
|
383
|
+
return key in self.data_session
|
|
384
384
|
|
|
385
385
|
def all(self) -> dict:
|
|
386
386
|
"""
|
|
@@ -404,32 +404,21 @@ class Config:
|
|
|
404
404
|
|
|
405
405
|
:return: list with available languages (user + app)
|
|
406
406
|
"""
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if os.path.exists(
|
|
410
|
-
for file in os.listdir(
|
|
407
|
+
langs_set = set()
|
|
408
|
+
path_app = os.path.join(self.get_app_path(), 'data', 'locale')
|
|
409
|
+
if os.path.exists(path_app):
|
|
410
|
+
for file in os.listdir(path_app):
|
|
411
411
|
if file.startswith('locale.') and file.endswith(".ini"):
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
path = os.path.join(self.get_user_path(), 'locale')
|
|
417
|
-
if os.path.exists(path):
|
|
418
|
-
for file in os.listdir(path):
|
|
412
|
+
langs_set.add(file.replace('locale.', '').replace('.ini', ''))
|
|
413
|
+
path_user = os.path.join(self.get_user_path(), 'locale')
|
|
414
|
+
if os.path.exists(path_user):
|
|
415
|
+
for file in os.listdir(path_user):
|
|
419
416
|
if file.startswith('locale.') and file.endswith(".ini"):
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
langs.append(lang_id)
|
|
423
|
-
|
|
424
|
-
# sort by name
|
|
425
|
-
langs.sort()
|
|
426
|
-
|
|
427
|
-
# make English first
|
|
417
|
+
langs_set.add(file.replace('locale.', '').replace('.ini', ''))
|
|
418
|
+
langs = sorted(langs_set)
|
|
428
419
|
if 'en' in langs:
|
|
429
420
|
langs.remove('en')
|
|
430
421
|
langs.insert(0, 'en')
|
|
431
|
-
|
|
432
|
-
# make Polish second
|
|
433
422
|
if 'pl' in langs:
|
|
434
423
|
langs.remove('pl')
|
|
435
424
|
langs.insert(1, 'pl')
|
|
@@ -469,7 +458,7 @@ class Config:
|
|
|
469
458
|
"""
|
|
470
459
|
self.data = self.provider.load(all)
|
|
471
460
|
if self.data is not None:
|
|
472
|
-
self.data = dict(sorted(self.data.items(), key=
|
|
461
|
+
self.data = dict(sorted(self.data.items(), key=itemgetter(0)))
|
|
473
462
|
|
|
474
463
|
def load_base_config(self):
|
|
475
464
|
"""
|
|
@@ -477,7 +466,7 @@ class Config:
|
|
|
477
466
|
"""
|
|
478
467
|
self.data_base = self.provider.load_base()
|
|
479
468
|
if self.data_base is not None:
|
|
480
|
-
self.data_base = dict(sorted(self.data_base.items(), key=
|
|
469
|
+
self.data_base = dict(sorted(self.data_base.items(), key=itemgetter(0)))
|
|
481
470
|
self.initialized_base = True
|
|
482
471
|
|
|
483
472
|
def from_base_config(self):
|
|
@@ -543,11 +532,12 @@ class Config:
|
|
|
543
532
|
if "app.env" not in self.data or not isinstance(self.data["app.env"], list):
|
|
544
533
|
return
|
|
545
534
|
list_loaded = []
|
|
535
|
+
conf = self.all()
|
|
546
536
|
for item in self.data["app.env"]:
|
|
547
537
|
if item['name'] is None or item['name'] == "":
|
|
548
538
|
continue
|
|
549
539
|
try:
|
|
550
|
-
value = str(item['value'].format(**
|
|
540
|
+
value = str(item['value'].format(**conf))
|
|
551
541
|
os.environ[item['name']] = value
|
|
552
542
|
list_loaded.append(item['name'])
|
|
553
543
|
except Exception as e:
|
|
@@ -561,4 +551,4 @@ class Config:
|
|
|
561
551
|
|
|
562
552
|
:param filename: filename
|
|
563
553
|
"""
|
|
564
|
-
self.provider.save(self.data, filename)
|
|
554
|
+
self.provider.save(self.data, filename)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date:
|
|
9
|
+
# Updated Date: 2025.08.15 03:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
@@ -32,29 +32,47 @@ class Note:
|
|
|
32
32
|
"""Setup calendar notes"""
|
|
33
33
|
self.counters_all = self.window.core.config.get("ctx.counters.all", True)
|
|
34
34
|
|
|
35
|
+
def _adjacent_months(self, year: int, month: int):
|
|
36
|
+
if month == 1:
|
|
37
|
+
py, pm = year - 1, 12
|
|
38
|
+
else:
|
|
39
|
+
py, pm = year, month - 1
|
|
40
|
+
if month == 12:
|
|
41
|
+
ny, nm = year + 1, 1
|
|
42
|
+
else:
|
|
43
|
+
ny, nm = year, month + 1
|
|
44
|
+
return (py, pm), (ny, nm)
|
|
45
|
+
|
|
35
46
|
def update(self):
|
|
36
47
|
"""Update on content change"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
ctrl_cal = self.window.controller.calendar
|
|
49
|
+
year = ctrl_cal.selected_year
|
|
50
|
+
month = ctrl_cal.selected_month
|
|
51
|
+
day = ctrl_cal.selected_day
|
|
40
52
|
|
|
41
53
|
if year is None or month is None or day is None:
|
|
42
54
|
return
|
|
43
55
|
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
ui_note = self.window.ui.calendar['note']
|
|
57
|
+
content = ui_note.toPlainText()
|
|
58
|
+
cal = self.window.core.calendar
|
|
59
|
+
note = cal.get_by_date(year, month, day)
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
changed = False
|
|
48
62
|
if note is None:
|
|
49
|
-
if content.strip()
|
|
63
|
+
if content.strip():
|
|
50
64
|
note = self.create(year, month, day)
|
|
51
65
|
note.content = content
|
|
52
|
-
|
|
66
|
+
cal.add(note)
|
|
67
|
+
changed = True
|
|
53
68
|
else:
|
|
54
|
-
note.content
|
|
55
|
-
|
|
69
|
+
if note.content != content:
|
|
70
|
+
note.content = content
|
|
71
|
+
cal.update(note)
|
|
72
|
+
changed = True
|
|
56
73
|
|
|
57
|
-
|
|
74
|
+
if changed:
|
|
75
|
+
self.refresh_num(year, month)
|
|
58
76
|
|
|
59
77
|
def update_content(
|
|
60
78
|
self,
|
|
@@ -69,13 +87,12 @@ class Note:
|
|
|
69
87
|
:param month: month
|
|
70
88
|
:param day: day
|
|
71
89
|
"""
|
|
90
|
+
ui_note = self.window.ui.calendar['note']
|
|
72
91
|
note = self.window.core.calendar.get_by_date(year, month, day)
|
|
73
|
-
if note is None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
self.window.ui.calendar['note'].setPlainText(note.content)
|
|
78
|
-
self.window.ui.calendar['note'].on_update()
|
|
92
|
+
new_text = "" if note is None else note.content
|
|
93
|
+
if ui_note.toPlainText() != new_text:
|
|
94
|
+
ui_note.setPlainText(new_text)
|
|
95
|
+
ui_note.on_update()
|
|
79
96
|
|
|
80
97
|
def update_label(
|
|
81
98
|
self,
|
|
@@ -90,15 +107,13 @@ class Note:
|
|
|
90
107
|
:param month: month
|
|
91
108
|
:param day: day
|
|
92
109
|
"""
|
|
93
|
-
suffix =
|
|
94
|
-
self.window.ui.calendar['note.label'].setText(trans('calendar.note.label')
|
|
110
|
+
suffix = f"{year:04d}-{month:02d}-{day:02d}"
|
|
111
|
+
self.window.ui.calendar['note.label'].setText(f"{trans('calendar.note.label')} ({suffix})")
|
|
95
112
|
|
|
96
113
|
def update_current(self):
|
|
97
114
|
"""Update label to current selected date"""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
day = self.window.ui.calendar['select'].currentDay
|
|
101
|
-
self.update_label(year, month, day)
|
|
115
|
+
select = self.window.ui.calendar['select']
|
|
116
|
+
self.update_label(select.currentYear, select.currentMonth, select.currentDay)
|
|
102
117
|
|
|
103
118
|
def update_status(
|
|
104
119
|
self,
|
|
@@ -115,16 +130,22 @@ class Note:
|
|
|
115
130
|
:param month: month
|
|
116
131
|
:param day: day
|
|
117
132
|
"""
|
|
118
|
-
|
|
133
|
+
cal = self.window.core.calendar
|
|
134
|
+
note = cal.get_by_date(year, month, day)
|
|
135
|
+
changed = False
|
|
119
136
|
if note is None:
|
|
120
137
|
note = self.create(year, month, day)
|
|
121
138
|
note.status = status
|
|
122
|
-
|
|
139
|
+
cal.add(note)
|
|
140
|
+
changed = True
|
|
123
141
|
else:
|
|
124
|
-
note.status
|
|
125
|
-
|
|
142
|
+
if note.status != status:
|
|
143
|
+
note.status = status
|
|
144
|
+
cal.update(note)
|
|
145
|
+
changed = True
|
|
126
146
|
|
|
127
|
-
|
|
147
|
+
if changed:
|
|
148
|
+
self.refresh_num(year, month)
|
|
128
149
|
|
|
129
150
|
def get_counts_around_month(
|
|
130
151
|
self,
|
|
@@ -138,27 +159,12 @@ class Note:
|
|
|
138
159
|
:param month: month
|
|
139
160
|
:return: combined counters
|
|
140
161
|
"""
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
next_month_start = datetime.datetime(year, month + 1, 1)
|
|
148
|
-
|
|
149
|
-
current = self.get_ctx_counters(
|
|
150
|
-
year,
|
|
151
|
-
month,
|
|
152
|
-
)
|
|
153
|
-
last = self.get_ctx_counters(
|
|
154
|
-
last_month_start.year,
|
|
155
|
-
last_month_start.month,
|
|
156
|
-
)
|
|
157
|
-
next = self.get_ctx_counters(
|
|
158
|
-
next_month_start.year,
|
|
159
|
-
next_month_start.month,
|
|
160
|
-
)
|
|
161
|
-
return {**last, **current, **next} # combine counters
|
|
162
|
+
(ly, lm), (ny, nm) = self._adjacent_months(year, month)
|
|
163
|
+
result: Dict[str, int] = {}
|
|
164
|
+
result.update(self.get_ctx_counters(ly, lm))
|
|
165
|
+
result.update(self.get_ctx_counters(year, month))
|
|
166
|
+
result.update(self.get_ctx_counters(ny, nm))
|
|
167
|
+
return result
|
|
162
168
|
|
|
163
169
|
def get_labels_counts_around_month(
|
|
164
170
|
self,
|
|
@@ -172,27 +178,12 @@ class Note:
|
|
|
172
178
|
:param month: month
|
|
173
179
|
:return: combined counters
|
|
174
180
|
"""
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
next_month_start = datetime.datetime(year, month + 1, 1)
|
|
182
|
-
|
|
183
|
-
current = self.get_ctx_labels_counters(
|
|
184
|
-
year,
|
|
185
|
-
month,
|
|
186
|
-
)
|
|
187
|
-
last = self.get_ctx_labels_counters(
|
|
188
|
-
last_month_start.year,
|
|
189
|
-
last_month_start.month,
|
|
190
|
-
)
|
|
191
|
-
next = self.get_ctx_labels_counters(
|
|
192
|
-
next_month_start.year,
|
|
193
|
-
next_month_start.month,
|
|
194
|
-
)
|
|
195
|
-
return {**last, **current, **next} # combine counters
|
|
181
|
+
(ly, lm), (ny, nm) = self._adjacent_months(year, month)
|
|
182
|
+
result: Dict[str, Dict[int, int]] = {}
|
|
183
|
+
result.update(self.get_ctx_labels_counters(ly, lm))
|
|
184
|
+
result.update(self.get_ctx_labels_counters(year, month))
|
|
185
|
+
result.update(self.get_ctx_labels_counters(ny, nm))
|
|
186
|
+
return result
|
|
196
187
|
|
|
197
188
|
def get_ctx_counters(
|
|
198
189
|
self,
|
|
@@ -206,18 +197,17 @@ class Note:
|
|
|
206
197
|
:param month: month
|
|
207
198
|
:return: ctx counters
|
|
208
199
|
"""
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return self.window.core.ctx.provider.get_ctx_count_by_day(
|
|
200
|
+
ctx = self.window.core.ctx
|
|
201
|
+
if self.counters_all:
|
|
202
|
+
search_string = None
|
|
203
|
+
search_content = False
|
|
204
|
+
filters = None
|
|
205
|
+
else:
|
|
206
|
+
search_string = ctx.get_search_string()
|
|
207
|
+
search_content = ctx.is_search_content()
|
|
208
|
+
filters = ctx.get_parsed_filters()
|
|
209
|
+
|
|
210
|
+
return ctx.provider.get_ctx_count_by_day(
|
|
221
211
|
year=year,
|
|
222
212
|
month=month,
|
|
223
213
|
day=None,
|
|
@@ -238,18 +228,17 @@ class Note:
|
|
|
238
228
|
:param month: month
|
|
239
229
|
:return: ctx counters
|
|
240
230
|
"""
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return self.window.core.ctx.provider.get_ctx_labels_count_by_day(
|
|
231
|
+
ctx = self.window.core.ctx
|
|
232
|
+
if self.counters_all:
|
|
233
|
+
search_string = None
|
|
234
|
+
search_content = False
|
|
235
|
+
filters = None
|
|
236
|
+
else:
|
|
237
|
+
search_string = ctx.get_search_string()
|
|
238
|
+
search_content = ctx.is_search_content()
|
|
239
|
+
filters = ctx.get_parsed_filters()
|
|
240
|
+
|
|
241
|
+
return ctx.provider.get_ctx_labels_count_by_day(
|
|
253
242
|
year=year,
|
|
254
243
|
month=month,
|
|
255
244
|
day=None,
|
|
@@ -305,26 +294,13 @@ class Note:
|
|
|
305
294
|
:param month: month
|
|
306
295
|
:return: combined notes existence
|
|
307
296
|
"""
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
current = self.window.core.calendar.get_notes_existence_by_day(
|
|
316
|
-
year,
|
|
317
|
-
month,
|
|
318
|
-
)
|
|
319
|
-
last = self.window.core.calendar.get_notes_existence_by_day(
|
|
320
|
-
last_month_start.year,
|
|
321
|
-
last_month_start.month,
|
|
322
|
-
)
|
|
323
|
-
next = self.window.core.calendar.get_notes_existence_by_day(
|
|
324
|
-
next_month_start.year,
|
|
325
|
-
next_month_start.month,
|
|
326
|
-
)
|
|
327
|
-
return {**last, **current, **next} # combine notes existence
|
|
297
|
+
(ly, lm), (ny, nm) = self._adjacent_months(year, month)
|
|
298
|
+
cal = self.window.core.calendar
|
|
299
|
+
result: Dict[str, Dict[int, int]] = {}
|
|
300
|
+
result.update(cal.get_notes_existence_by_day(ly, lm))
|
|
301
|
+
result.update(cal.get_notes_existence_by_day(year, month))
|
|
302
|
+
result.update(cal.get_notes_existence_by_day(ny, nm))
|
|
303
|
+
return result
|
|
328
304
|
|
|
329
305
|
def refresh_num(self, year: int, month: int):
|
|
330
306
|
"""
|
|
@@ -342,6 +318,8 @@ class Note:
|
|
|
342
318
|
|
|
343
319
|
:param state: state
|
|
344
320
|
"""
|
|
321
|
+
if self.counters_all == state:
|
|
322
|
+
return
|
|
345
323
|
self.counters_all = state
|
|
346
324
|
self.window.core.config.set("ctx.counters.all", state)
|
|
347
325
|
self.window.core.config.save()
|
|
@@ -355,17 +333,14 @@ class Note:
|
|
|
355
333
|
"""
|
|
356
334
|
dt = "" # TODO: add to config append date/time
|
|
357
335
|
# dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ":\n--------------------------\n"
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
prev_text += "\n\n"
|
|
361
|
-
new_text = prev_text + dt + text.strip()
|
|
362
|
-
self.window.ui.calendar['note'].setText(new_text)
|
|
363
|
-
self.update()
|
|
364
|
-
|
|
365
|
-
# move cursor to end
|
|
366
|
-
cursor = self.window.ui.calendar['note'].textCursor()
|
|
336
|
+
editor = self.window.ui.calendar['note']
|
|
337
|
+
cursor = editor.textCursor()
|
|
367
338
|
cursor.movePosition(QTextCursor.End)
|
|
368
|
-
|
|
339
|
+
if not editor.document().isEmpty():
|
|
340
|
+
cursor.insertText("\n\n")
|
|
341
|
+
cursor.insertText(dt + text.strip())
|
|
342
|
+
editor.setTextCursor(cursor)
|
|
343
|
+
self.update()
|
|
369
344
|
|
|
370
345
|
def clear_note(self):
|
|
371
346
|
"""Clear note"""
|
|
@@ -379,4 +354,4 @@ class Note:
|
|
|
379
354
|
|
|
380
355
|
:return: notepad text
|
|
381
356
|
"""
|
|
382
|
-
return self.window.ui.calendar['note'].toPlainText()
|
|
357
|
+
return self.window.ui.calendar['note'].toPlainText()
|