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.
Files changed (86) hide show
  1. pygpt_net/CHANGELOG.txt +10 -0
  2. pygpt_net/__init__.py +1 -1
  3. pygpt_net/config.py +55 -65
  4. pygpt_net/controller/calendar/note.py +101 -126
  5. pygpt_net/controller/chat/chat.py +38 -35
  6. pygpt_net/controller/chat/render.py +154 -214
  7. pygpt_net/controller/chat/stream.py +51 -25
  8. pygpt_net/controller/config/config.py +39 -42
  9. pygpt_net/controller/config/field/checkbox.py +16 -12
  10. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  11. pygpt_net/controller/config/field/cmd.py +51 -57
  12. pygpt_net/controller/config/field/combo.py +33 -16
  13. pygpt_net/controller/config/field/dictionary.py +48 -55
  14. pygpt_net/controller/config/field/input.py +50 -32
  15. pygpt_net/controller/config/field/slider.py +40 -45
  16. pygpt_net/controller/config/field/textarea.py +20 -6
  17. pygpt_net/controller/config/placeholder.py +110 -231
  18. pygpt_net/controller/ctx/common.py +3 -2
  19. pygpt_net/controller/ctx/ctx.py +68 -129
  20. pygpt_net/controller/lang/mapping.py +57 -95
  21. pygpt_net/controller/lang/plugins.py +64 -55
  22. pygpt_net/controller/lang/settings.py +39 -38
  23. pygpt_net/controller/layout/layout.py +176 -109
  24. pygpt_net/controller/mode/mode.py +88 -85
  25. pygpt_net/controller/model/model.py +73 -73
  26. pygpt_net/controller/plugins/plugins.py +201 -240
  27. pygpt_net/controller/plugins/presets.py +54 -55
  28. pygpt_net/controller/plugins/settings.py +54 -69
  29. pygpt_net/controller/presets/presets.py +292 -297
  30. pygpt_net/controller/theme/theme.py +72 -81
  31. pygpt_net/controller/ui/mode.py +110 -129
  32. pygpt_net/controller/ui/tabs.py +69 -90
  33. pygpt_net/controller/ui/ui.py +47 -56
  34. pygpt_net/controller/ui/vision.py +24 -23
  35. pygpt_net/core/bridge/bridge.py +5 -5
  36. pygpt_net/core/command/command.py +149 -219
  37. pygpt_net/core/ctx/ctx.py +94 -146
  38. pygpt_net/core/debug/debug.py +48 -58
  39. pygpt_net/core/models/models.py +74 -112
  40. pygpt_net/core/modes/modes.py +13 -21
  41. pygpt_net/core/plugins/plugins.py +154 -177
  42. pygpt_net/core/presets/presets.py +103 -176
  43. pygpt_net/core/render/web/body.py +183 -193
  44. pygpt_net/core/render/web/renderer.py +331 -449
  45. pygpt_net/core/text/utils.py +28 -44
  46. pygpt_net/core/tokens/tokens.py +104 -203
  47. pygpt_net/data/config/config.json +2 -2
  48. pygpt_net/data/config/models.json +2 -2
  49. pygpt_net/item/ctx.py +141 -139
  50. pygpt_net/plugin/agent/plugin.py +2 -1
  51. pygpt_net/plugin/audio_output/plugin.py +5 -2
  52. pygpt_net/plugin/base/plugin.py +77 -93
  53. pygpt_net/plugin/bitbucket/plugin.py +3 -2
  54. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  55. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  56. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  57. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  58. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  59. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  60. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  61. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  62. pygpt_net/plugin/experts/plugin.py +2 -2
  63. pygpt_net/plugin/facebook/plugin.py +3 -4
  64. pygpt_net/plugin/github/plugin.py +4 -2
  65. pygpt_net/plugin/google/plugin.py +3 -3
  66. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  67. pygpt_net/plugin/mailer/plugin.py +3 -5
  68. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  69. pygpt_net/plugin/real_time/plugin.py +52 -60
  70. pygpt_net/plugin/slack/plugin.py +3 -4
  71. pygpt_net/plugin/telegram/plugin.py +3 -4
  72. pygpt_net/plugin/twitter/plugin.py +3 -4
  73. pygpt_net/ui/base/config_dialog.py +83 -101
  74. pygpt_net/ui/base/context_menu.py +48 -46
  75. pygpt_net/ui/layout/toolbox/presets.py +41 -41
  76. pygpt_net/ui/widget/calendar/select.py +86 -70
  77. pygpt_net/ui/widget/lists/attachment.py +86 -44
  78. pygpt_net/ui/widget/lists/base_list_combo.py +85 -33
  79. pygpt_net/ui/widget/lists/context.py +135 -188
  80. pygpt_net/ui/widget/lists/preset.py +59 -61
  81. pygpt_net/ui/widget/textarea/web.py +18 -14
  82. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/METADATA +13 -3
  83. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/RECORD +86 -86
  84. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/LICENSE +0 -0
  85. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.4.dist-info}/WHEEL +0 -0
  86. {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.2"
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.07.21 15:00:00 #
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 = {} # user config
50
- self.data_base = {} # base config
51
- self.data_session = {} # temporary config (session only)
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 # verbose on/off
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: # DISABLE in tests!!!
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: # DISABLE in tests!!!
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 = ["img", "capture", "upload"]
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
- if not os.path.exists(path):
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.is_compiled(): # if compiled with pyinstaller
212
- return os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
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
- return os.path.abspath(os.path.dirname(__file__))
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
- os = self.window.core.platforms.get_os()
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} ({os}, {architecture})")
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 = re.search(r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format("__version__"), data)
266
- return result.group(1)
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 = re.search(r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format("__build__"), data)
284
- return result.group(1)
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
- if key in self.data:
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
- if key in self.data_session:
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') # if pytest
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
- if self.data is None:
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
- if key in self.data_session:
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
- langs = []
408
- path = os.path.join(self.get_app_path(), 'data', 'locale')
409
- if os.path.exists(path):
410
- for file in os.listdir(path):
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
- lang_id = file.replace('locale.', '').replace('.ini', '')
413
- if lang_id not in langs:
414
- langs.append(lang_id)
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
- lang_id = file.replace('locale.', '').replace('.ini', '')
421
- if lang_id not in langs:
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=lambda item: item[0])) # sort by 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=lambda item: item[0])) # sort by 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(**self.all()))
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: 2024.12.14 08:00:00 #
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
- year = self.window.controller.calendar.selected_year
38
- month = self.window.controller.calendar.selected_month
39
- day = self.window.controller.calendar.selected_day
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
- content = self.window.ui.calendar['note'].toPlainText()
45
- note = self.window.core.calendar.get_by_date(year, month, day)
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
- # update or create note
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
- self.window.core.calendar.add(note)
66
+ cal.add(note)
67
+ changed = True
53
68
  else:
54
- note.content = content
55
- self.window.core.calendar.update(note)
69
+ if note.content != content:
70
+ note.content = content
71
+ cal.update(note)
72
+ changed = True
56
73
 
57
- self.refresh_num(year, month) # update note cells when note is changed
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
- self.window.ui.calendar['note'].setPlainText("")
75
- self.window.ui.calendar['note'].on_update()
76
- else:
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 = datetime.datetime(year, month, day).strftime("%Y-%m-%d")
94
- self.window.ui.calendar['note.label'].setText(trans('calendar.note.label') + " (" + suffix + ")")
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
- year = self.window.ui.calendar['select'].currentYear
99
- month = self.window.ui.calendar['select'].currentMonth
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
- note = self.window.core.calendar.get_by_date(year, month, day)
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
- self.window.core.calendar.add(note)
139
+ cal.add(note)
140
+ changed = True
123
141
  else:
124
- note.status = status
125
- self.window.core.calendar.update(note)
142
+ if note.status != status:
143
+ note.status = status
144
+ cal.update(note)
145
+ changed = True
126
146
 
127
- self.refresh_num(year, month) # update note cells when note is changed
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
- current_month_start = datetime.datetime(year, month, 1)
142
- last_month_start = (current_month_start - datetime.timedelta(days=1)).replace(day=1)
143
-
144
- if month == 12:
145
- next_month_start = datetime.datetime(year + 1, 1, 1)
146
- else:
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
- current_month_start = datetime.datetime(year, month, 1)
176
- last_month_start = (current_month_start - datetime.timedelta(days=1)).replace(day=1)
177
-
178
- if month == 12:
179
- next_month_start = datetime.datetime(year + 1, 1, 1)
180
- else:
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
- # default values (no filters)
210
- search_string = None
211
- search_content = False
212
- filters = None
213
-
214
- # + filters
215
- if not self.counters_all:
216
- search_string = self.window.core.ctx.get_search_string()
217
- search_content = self.window.core.ctx.is_search_content()
218
- filters = self.window.core.ctx.get_parsed_filters()
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
- # default values (no filters)
242
- search_string = None
243
- search_content = False
244
- filters = None
245
-
246
- # + filters
247
- if not self.counters_all:
248
- search_string = self.window.core.ctx.get_search_string()
249
- search_content = self.window.core.ctx.is_search_content()
250
- filters = self.window.core.ctx.get_parsed_filters()
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
- current_month_start = datetime.datetime(year, month, 1)
309
- last_month_start = (current_month_start - datetime.timedelta(days=1)).replace(day=1)
310
- if month == 12:
311
- next_month_start = datetime.datetime(year + 1, 1, 1)
312
- else:
313
- next_month_start = datetime.datetime(year, month + 1, 1)
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
- prev_text = self.window.ui.calendar['note'].toPlainText()
359
- if prev_text != "":
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
- self.window.ui.calendar['note'].setTextCursor(cursor)
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()