psiutils 0.2.6__tar.gz → 0.2.19__tar.gz

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 (68) hide show
  1. {psiutils-0.2.6 → psiutils-0.2.19}/PKG-INFO +6 -2
  2. {psiutils-0.2.6 → psiutils-0.2.19}/pyproject.toml +6 -2
  3. psiutils-0.2.19/src/psiutils/__init__.py +0 -0
  4. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/_about_frame.py +3 -1
  5. psiutils-0.2.19/src/psiutils/_date_picker.py +210 -0
  6. psiutils-0.2.19/src/psiutils/_notify.py +17 -0
  7. psiutils-0.2.19/src/psiutils/_version.py +1 -0
  8. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/buttons.py +14 -7
  9. psiutils-0.2.19/src/psiutils/constants.py +92 -0
  10. psiutils-0.2.19/src/psiutils/icons/backup.png +0 -0
  11. psiutils-0.2.19/src/psiutils/icons/download.png +0 -0
  12. psiutils-0.2.19/src/psiutils/icons/restore.png +0 -0
  13. psiutils-0.2.19/src/psiutils/icons/restore_database.png +0 -0
  14. psiutils-0.2.19/src/psiutils/icons/restore_page.png +0 -0
  15. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/messagebox.py +12 -10
  16. psiutils-0.2.19/src/psiutils/text.py +121 -0
  17. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/utilities.py +15 -4
  18. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/widgets.py +10 -5
  19. psiutils-0.2.6/src/psiutils/__init__.py +0 -15
  20. psiutils-0.2.6/src/psiutils/_notify.py +0 -11
  21. psiutils-0.2.6/src/psiutils/_version.py +0 -1
  22. psiutils-0.2.6/src/psiutils/constants.py +0 -65
  23. psiutils-0.2.6/src/psiutils/text.py +0 -46
  24. {psiutils-0.2.6 → psiutils-0.2.19}/README.md +0 -0
  25. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/_logger.py +0 -0
  26. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/drag_manager.py +0 -0
  27. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/errors.py +0 -0
  28. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icecream_init.py +0 -0
  29. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/build.png +0 -0
  30. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/cancel.png +0 -0
  31. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/check.png +0 -0
  32. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/clear.png +0 -0
  33. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/code.png +0 -0
  34. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/compare.png +0 -0
  35. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/copy_clipboard.png +0 -0
  36. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/copy_docs.png +0 -0
  37. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/delete.png +0 -0
  38. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/diff.png +0 -0
  39. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/done.png +0 -0
  40. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/edit.png +0 -0
  41. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/gear.png +0 -0
  42. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/new.png +0 -0
  43. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/next.png +0 -0
  44. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/open.png +0 -0
  45. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/pause.png +0 -0
  46. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/preferences.png +0 -0
  47. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/previous.png +0 -0
  48. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/process.png +0 -0
  49. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/redo.png +0 -0
  50. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/refresh.png +0 -0
  51. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/rename.png +0 -0
  52. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/report.png +0 -0
  53. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/reset.png +0 -0
  54. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/revert.png +0 -0
  55. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/save.png +0 -0
  56. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/script.png +0 -0
  57. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/search.png +0 -0
  58. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/send.png +0 -0
  59. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/start.png +0 -0
  60. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/update.png +0 -0
  61. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/upgrade.png +0 -0
  62. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/icons/windows.png +0 -0
  63. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/images/icon-error.png +0 -0
  64. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/images/icon-info.png +0 -0
  65. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/images/icon-query.png +0 -0
  66. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/known_paths.py +0 -0
  67. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/menus.py +0 -0
  68. {psiutils-0.2.6 → psiutils-0.2.19}/src/psiutils/treeview.py +0 -0
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: psiutils
3
- Version: 0.2.6
3
+ Version: 0.2.19
4
4
  Summary: Various TKinter utilities.
5
5
  Author: Jeff
6
6
  Author-email: Jeff <<jeffwatkins2000@gmail.com>>
7
+ Requires-Dist: appdirs>=1.4.4
7
8
  Requires-Dist: iniconfig>=2.1.0
8
9
  Requires-Dist: markdown>=3.8.2
9
10
  Requires-Dist: packaging>=25.0
@@ -11,12 +12,15 @@ Requires-Dist: pillow>=11.3.0
11
12
  Requires-Dist: pluggy>=1.6.0
12
13
  Requires-Dist: psi-toml>=0.0.12
13
14
  Requires-Dist: psiconfig>=0.0.14
15
+ Requires-Dist: pycairo>=1.28.0
14
16
  Requires-Dist: pygments>=2.19.2
17
+ Requires-Dist: pygobject>=3.54.2
18
+ Requires-Dist: structlog>=25.4.0
15
19
  Requires-Dist: tkinterweb>=4.4.4
16
20
  Requires-Dist: tkinterweb-tkhtml>=1.1.1
17
21
  Requires-Dist: tomli>=2.2.1
18
22
  Requires-Dist: tomli-w>=1.2.0
19
- Requires-Python: ==3.13.*
23
+ Requires-Python: >=3.10, <3.13
20
24
  Description-Content-Type: text/markdown
21
25
 
22
26
  # psiutils
@@ -1,14 +1,15 @@
1
1
  [project]
2
2
  name = "psiutils"
3
- version = "0.2.6"
3
+ version = "0.2.19"
4
4
  description = "Various TKinter utilities."
5
5
  authors = [{name = "Jeff", "email" = "<jeffwatkins2000@gmail.com>"}]
6
- requires-python = '==3.13.*'
6
+ requires-python = '>= 3.10, < 3.13'
7
7
  readme = "README.md"
8
8
 
9
9
  include = ["HISTORY.md", "src/psiutils/icons/*.png"]
10
10
 
11
11
  dependencies = [
12
+ "appdirs>=1.4.4",
12
13
  "iniconfig>=2.1.0",
13
14
  "markdown>=3.8.2",
14
15
  "packaging>=25.0",
@@ -16,7 +17,10 @@ dependencies = [
16
17
  "pluggy>=1.6.0",
17
18
  "psi-toml>=0.0.12",
18
19
  "psiconfig>=0.0.14",
20
+ "pycairo>=1.28.0",
19
21
  "pygments>=2.19.2",
22
+ "pygobject>=3.54.2",
23
+ "structlog>=25.4.0",
20
24
  "tkinterweb>=4.4.4",
21
25
  "tkinterweb-tkhtml>=1.1.1",
22
26
  "tomli>=2.2.1",
File without changes
@@ -8,7 +8,9 @@ import markdown
8
8
  from psiutils.constants import PAD, Pad
9
9
  from psiutils.utilities import window_resize
10
10
 
11
- from psiutils import text as txt
11
+ from psiutils.text import Text
12
+
13
+ txt = Text()
12
14
 
13
15
  DEFAULT_GEOMETRY = '400x200'
14
16
  HISTORY_GEOMETRY = '800x750'
@@ -0,0 +1,210 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk
3
+ import tkinter.font as tkFont
4
+ from tkcalendar import DateEntry
5
+ from functools import partial
6
+ from datetime import datetime, timedelta
7
+ from dateutil.parser import parse
8
+
9
+ from text import Text
10
+
11
+ txt = Text()
12
+
13
+ PAD = 2
14
+ TIME_WIDTH = 3
15
+ INCREMENT_BUTTON_SIZE = 2
16
+ INCREMENT_BUTTON_FONT_SIZE = 8
17
+ DATE_FORMAT = '%d/%m/%Y'
18
+ PICKER_DATE_PATTERN = 'dd/mm/yyyy',
19
+ MAX_HOURS = 23
20
+ MAX_MINS = 59
21
+ TALL_COMBO_PADDING = 6
22
+
23
+
24
+ class DatePicker(tk.Frame):
25
+ def __init__(
26
+ self, master: tk.Frame,
27
+ initial_date: datetime = None,
28
+ date_format: str = ''):
29
+ super().__init__(master)
30
+ if not initial_date:
31
+ initial_date = datetime.now()
32
+ if not date_format:
33
+ date_format = DATE_FORMAT
34
+
35
+ self._date_input = tk.StringVar(
36
+ value=initial_date.strftime(DATE_FORMAT))
37
+
38
+ style = ttk.Style()
39
+ style.configure(
40
+ 'Increment.TButton',
41
+ font=('Helvetica', INCREMENT_BUTTON_FONT_SIZE),
42
+ padding=0,)
43
+ style.configure('Tall.TCombobox', padding=TALL_COMBO_PADDING)
44
+
45
+ main_frame = self._picker()
46
+ main_frame.pack()
47
+
48
+ def _picker(self) -> tk.Frame:
49
+ frame = ttk.Frame(self)
50
+
51
+ column = 0
52
+ date_picker = self._date_picker(frame, self._date_input)
53
+ date_picker.grid(row=0, column=column, rowspan=2, sticky=tk.NS)
54
+
55
+ column += 1
56
+ button = ttk.Button(
57
+ frame,
58
+ text=txt.INCREMENT_ARROW,
59
+ command=partial(self._date_increment, self._date_input),
60
+ width=INCREMENT_BUTTON_SIZE,
61
+ style='Increment.TButton',
62
+ )
63
+ button.grid(row=0, column=column, padx=PAD)
64
+
65
+ button = ttk.Button(
66
+ frame,
67
+ text=txt.DECREMENT_ARROW,
68
+ command=partial(self._date_increment, self._date_input, -1),
69
+ width=INCREMENT_BUTTON_SIZE,
70
+ style='Increment.TButton',
71
+ )
72
+ button.grid(row=1, column=column, padx=PAD)
73
+
74
+ column += 1
75
+
76
+ return frame
77
+
78
+ def _date_picker(
79
+ self, master: tk.Frame, textvariable: tk.StringVar) -> DateEntry:
80
+ event_date = datetime.now()
81
+ return DateEntry(
82
+ master,
83
+ date_pattern=PICKER_DATE_PATTERN,
84
+ year=event_date.year,
85
+ month=event_date.month,
86
+ day=event_date.day,
87
+ textvariable=textvariable,
88
+ )
89
+
90
+ @property
91
+ def date(self) -> datetime:
92
+ return parse(self._date_input.get())
93
+
94
+ def _date_increment(
95
+ self,
96
+ textvariable: tk.StringVar,
97
+ increment: int = 1, *args) -> None:
98
+ date = parse(textvariable.get(), dayfirst=True).date()
99
+ new_date = date + timedelta(days=increment)
100
+ textvariable.set(new_date.strftime(DATE_FORMAT))
101
+
102
+
103
+ class TimePicker(tk.Frame):
104
+ def __init__(
105
+ self, master: tk.Frame,
106
+ use_seconds: bool = False,
107
+ use_labels: bool = False):
108
+ super().__init__(master)
109
+ self.use_seconds = use_seconds
110
+ self.use_labels = use_labels
111
+
112
+ self._hour_input = tk.StringVar(value='00')
113
+ self._minute_input = tk.StringVar(value='00')
114
+ self._second_input = tk.StringVar(value='00')
115
+
116
+ style = ttk.Style()
117
+ style.configure('Increment.TButton', font=('Helvetica', 8))
118
+
119
+ main_frame = self._picker()
120
+ main_frame.grid(row=0, column=0)
121
+
122
+ def _picker(self) -> tk.Frame:
123
+ frame = ttk.Frame(self)
124
+
125
+ column = 0
126
+ row = 0
127
+ if self.use_labels:
128
+ label = ttk.Label(frame, text='Hour')
129
+ label.grid(row=row, column=column, sticky=tk.W)
130
+
131
+ label = ttk.Label(frame, text='Mins')
132
+ label.grid(row=row, column=column+1, sticky=tk.W)
133
+
134
+ if self.use_seconds:
135
+ label = ttk.Label(frame, text='Secs')
136
+ label.grid(row=row, column=column+5, sticky=tk.E)
137
+
138
+ row += 1
139
+ hour_timer = self._timer_element(frame, self._hour_input, MAX_HOURS)
140
+ hour_timer.grid(row=row, column=column)
141
+
142
+ column += 1
143
+ minute_timer = self._timer_element(frame, self._minute_input)
144
+ minute_timer.grid(row=row, column=column)
145
+
146
+ if self.use_seconds:
147
+ column += 1
148
+ second_timer = self._timer_element(frame, self._second_input)
149
+ second_timer.grid(row=row, column=column)
150
+
151
+ return frame
152
+
153
+ def _timer_element(
154
+ self,
155
+ master: tk.Frame,
156
+ textvariable: tk.StringVar,
157
+ max_value: int = MAX_MINS,
158
+ ) -> tk.Frame:
159
+ frame = ttk.Frame(master)
160
+ column = 0
161
+
162
+ combobox = ttk.Combobox(
163
+ frame,
164
+ textvariable=textvariable,
165
+ values=[f'{x:02d}' for x in range(max_value+1)],
166
+ width=TIME_WIDTH,
167
+ style='Tall.TCombobox',
168
+ )
169
+ combobox.grid(row=0, column=column, rowspan=2, sticky=tk.W)
170
+
171
+ column += 1
172
+
173
+ button = ttk.Button(
174
+ frame,
175
+ text=txt.INCREMENT_ARROW,
176
+ command=partial(self._time_increment, textvariable, 1, max_value),
177
+ width=INCREMENT_BUTTON_SIZE,
178
+ style='Increment.TButton',
179
+ )
180
+ button.grid(row=0, column=column, padx=PAD)
181
+
182
+ button = ttk.Button(
183
+ frame,
184
+ text=txt.DECREMENT_ARROW,
185
+ command=partial(self._time_increment, textvariable, -1, max_value),
186
+ width=INCREMENT_BUTTON_SIZE,
187
+ style='Increment.TButton',
188
+ )
189
+ button.grid(row=1, column=column, padx=PAD)
190
+ return frame
191
+
192
+ def _time_increment(
193
+ self,
194
+ textvariable: tk.StringVar,
195
+ increment,
196
+ max_value) -> None:
197
+ value = int(textvariable.get()) + increment
198
+ if value < 0:
199
+ value = max_value
200
+ if value > max_value:
201
+ value = 0
202
+ textvariable.set(f'{value:02d}')
203
+
204
+ @property
205
+ def time(self) -> datetime:
206
+ return datetime(
207
+ hours=int(self._hour_input.get()),
208
+ minutes=int(self._minute_input.get()),
209
+ seconds=int(self._second_input.get()),
210
+ )
@@ -0,0 +1,17 @@
1
+ """Provide notifications on Linux platforms."""
2
+
3
+ import platform
4
+
5
+ if platform.system() == 'Linux':
6
+ import gi
7
+ gi.require_version('Gio', '2.0')
8
+ gi.require_version('GioUnix', '2.0')
9
+ gi.require_version('Notify', '0.7')
10
+ from gi.repository import Notify
11
+
12
+
13
+ def _notify(title: str, message: str) -> None:
14
+ if platform.system() != 'Linux':
15
+ return
16
+ Notify.init(title)
17
+ Notify.Notification.new(message).show()
@@ -0,0 +1 @@
1
+ __version__ = '0.2.19'
@@ -4,10 +4,12 @@ from tkinter import ttk
4
4
  from pathlib import Path
5
5
  from PIL import Image, ImageTk
6
6
 
7
- import psiutils.text as txt
7
+ from psiutils.text import Text
8
8
 
9
- from .constants import PAD, Pad
10
- from .widgets import enter_widget, clickable_widget, HAND
9
+ from psiutils.constants import PAD, Pad
10
+ from psiutils.widgets import enter_widget, clickable_widget, HAND
11
+
12
+ txt = Text()
11
13
 
12
14
 
13
15
  class IconButton(ttk.Frame):
@@ -16,8 +18,8 @@ class IconButton(ttk.Frame):
16
18
  master,
17
19
  button_text,
18
20
  icon,
19
- dimmable: bool = False,
20
21
  command=None,
22
+ dimmable: bool = False,
21
23
  sticky: str = '',
22
24
  icon_path: str = '',
23
25
  **kwargs):
@@ -48,7 +50,7 @@ class IconButton(ttk.Frame):
48
50
  def __repr__(self) -> str:
49
51
  return f'IconButton: {self.text} {self.icon}'
50
52
 
51
- def state(self) -> dict:
53
+ def state(self, *args, **kwargs) -> dict:
52
54
  return self._state
53
55
 
54
56
  def enable(self, enable: bool = True) -> None:
@@ -124,8 +126,8 @@ class ButtonFrame(ttk.Frame):
124
126
  def icon_button(
125
127
  self,
126
128
  id_: str,
127
- dimmable: bool = False,
128
- command: object = None) -> IconButton:
129
+ command: object = None,
130
+ dimmable: bool = False,) -> IconButton:
129
131
  button = self.icon_buttons[id_]
130
132
  button.dimmable = dimmable
131
133
  button.command = command
@@ -215,6 +217,7 @@ def enable_buttons(buttons: list[Button], enable: bool = True):
215
217
 
216
218
 
217
219
  icon_buttons = {
220
+ 'backup': (txt.BACKUP, 'backup'),
218
221
  'build': (txt.BUILD, 'build'),
219
222
  'check': (txt.CHECK, 'check'),
220
223
  'clear': (txt.CLEAR, 'clear'),
@@ -225,6 +228,7 @@ icon_buttons = {
225
228
  'copy_docs': (txt.COPY, 'copy_docs'),
226
229
  'copy_clipboard': (txt.COPY, 'copy_clipboard'),
227
230
  'delete': (txt.DELETE, 'delete'),
231
+ 'download': (txt.DOWNLOAD, 'download'),
228
232
  'diff': (txt.DIFF, 'diff'),
229
233
  'done': (txt.DONE, 'done'),
230
234
  'edit': (txt.EDIT, 'edit'),
@@ -241,6 +245,9 @@ icon_buttons = {
241
245
  'rename': (txt.RENAME, 'rename'),
242
246
  'report': (txt.REPORT, 'report'),
243
247
  'reset': (txt.RESET, 'reset'),
248
+ 'restore': (txt.RESTORE, 'restore'),
249
+ 'restore_database': (txt.RESTORE, 'restore_database'),
250
+ 'restore_page': (txt.RESTORE, 'restore_page'),
244
251
  'revert': (txt.REVERT, 'revert'),
245
252
  'run': (txt.RUN, 'start'),
246
253
  'save': (txt.SAVE, 'save'),
@@ -0,0 +1,92 @@
1
+ """Constants for the tkinter psiutils."""
2
+ from enum import Enum, auto
3
+
4
+ # from .utilities import invert
5
+ from .known_paths import get_documents_dir, get_downloads_dir
6
+
7
+ DEFAULT_GEOMETRY = '500x400'
8
+
9
+ # # TODO is this needed with Status?
10
+ # DIALOG_STATUS: dict = {
11
+ # 'yes': True,
12
+ # 'no': False,
13
+ # 'cancel': None,
14
+ # 'null': 0,
15
+ # 'undefined': 0,
16
+ # 'exit': 1,
17
+ # 'ok': 2,
18
+ # 'updated': 3,
19
+ # 'error': 4,
20
+ # }
21
+ # DIALOG_STATUS = invert(DIALOG_STATUS)
22
+
23
+ # # TODO is this needed with Mode?
24
+ # MODES: dict[int, str] | dict[str, int] = {
25
+ # 0: 'view',
26
+ # 1: 'new',
27
+ # 2: 'edit',
28
+ # 3: 'delete'
29
+ # }
30
+ # MODES = invert(MODES)
31
+
32
+ # GUI
33
+ PAD = 5
34
+ PADR = (0, PAD)
35
+ PADL = (PAD, 0)
36
+ PADT = (PAD, 0)
37
+ PADB = (0, PAD)
38
+ LARGE_FONT = ('Arial', 16)
39
+ BOLD_FONT = ('Arial', 12, 'bold')
40
+
41
+ # Colours
42
+ COLOURS = {
43
+ 'pale-umber': '#ffcfa0',
44
+ 'pale-red': '#ff9999',
45
+ 'pale-grey': '#e8e8e8'
46
+ }
47
+
48
+ CSV_FILE_TYPES = (
49
+ ('csv files', '*.csv'),
50
+ ('All files', '*.*')
51
+ )
52
+
53
+ TXT_FILE_TYPES = (
54
+ ('csv files', '*.txt'),
55
+ ('All files', '*.*')
56
+ )
57
+
58
+ XML_FILE_TYPES = (
59
+ ('xml files', '*.xml'),
60
+ ('All files', '*.*')
61
+ )
62
+
63
+
64
+ class Pad():
65
+ W = (0, PAD)
66
+ E = (PAD, 0)
67
+ N = (PAD, 0)
68
+ S = (0, PAD)
69
+
70
+
71
+ DOCUMENTS_DIR = get_documents_dir()
72
+ DOWNLOADS_DIR = get_downloads_dir()
73
+
74
+
75
+ class Mode(Enum):
76
+ VIEW = auto()
77
+ NEW = auto()
78
+ EDIT = auto()
79
+ DELETE = auto()
80
+
81
+
82
+ class Status(Enum):
83
+ YES = True
84
+ NO = False
85
+ CANCEL = None
86
+ NULL = 0
87
+ EXIT = 1
88
+ OK = 2
89
+ SUCCESS = 3
90
+ UPDATED = 4
91
+ ERROR = 5
92
+ WARNING = 6
@@ -5,9 +5,11 @@ from tkinter import font
5
5
  from pathlib import Path
6
6
  from PIL import ImageTk, Image
7
7
 
8
- from psiutils.constants import PAD
8
+ from psiutils.constants import PAD, Status
9
9
 
10
- import psiutils.text as text
10
+ from psiutils.text import Text
11
+
12
+ txt = Text()
11
13
 
12
14
  icons = {
13
15
  'info': 'icon-info.png',
@@ -113,29 +115,29 @@ class MessageBox():
113
115
  def _get_buttons(self, frame: ttk.Frame) -> dict[str, ttk.Button]:
114
116
  return {
115
117
  'ok': ttk.Button(
116
- frame, text=text.OK, command=self._ok, underline=0),
118
+ frame, text=txt.OK, command=self._ok, underline=0),
117
119
  'yes': ttk.Button(
118
- frame, text=text.YES, command=self._yes, underline=0),
120
+ frame, text=txt.YES, command=self._yes, underline=0),
119
121
  'no': ttk.Button(
120
- frame, text=text.NO, command=self._no, underline=0),
122
+ frame, text=txt.NO, command=self._no, underline=0),
121
123
  'cancel': ttk.Button(
122
- frame, text=text.CANCEL, command=self._cancel, underline=0),
124
+ frame, text=txt.CANCEL, command=self._cancel, underline=0),
123
125
  }
124
126
 
125
127
  def _ok(self, *args) -> None:
126
- self.status = STATUS['cancel']
128
+ self.status = Status.OK
127
129
  self._dismiss()
128
130
 
129
131
  def _yes(self, *args) -> None:
130
- self.status = STATUS['yes']
132
+ self.status = Status.YES
131
133
  self._dismiss()
132
134
 
133
135
  def _no(self, *args) -> None:
134
- self.status = STATUS['no']
136
+ self.status = Status.NO
135
137
  self._dismiss()
136
138
 
137
139
  def _cancel(self, *args) -> None:
138
- self.status = STATUS['cancel']
140
+ self.status = Status.CANCEL
139
141
  self._dismiss()
140
142
 
141
143
  def _dismiss(self, *args) -> None:
@@ -0,0 +1,121 @@
1
+ """ Handle standard text for psiutils."""
2
+
3
+ from copy import copy
4
+
5
+
6
+ psi_strings = {
7
+ 'BACKUP': 'Backup',
8
+ 'BUILD': 'Build',
9
+ 'CANCEL': 'Cancel',
10
+ 'CHECK': 'Check',
11
+ 'CLEAR': 'Clear',
12
+ 'CLOSE': 'Close',
13
+ 'CODE': 'Code',
14
+ 'COMPARE': 'Compare',
15
+ 'CONFIG': 'Config',
16
+ 'COPY': 'Copy',
17
+ 'DECREMENT_ARROW': '▼',
18
+ 'DELETE': 'Delete',
19
+ 'DELETE_THESE_ITEMS': 'Are you sure you want to delete these item(s)?',
20
+ 'DIFF': 'Diff',
21
+ 'DONE': 'Done',
22
+ 'DOWNLOAD': 'Download',
23
+ 'EDIT': 'Edit',
24
+ 'ELLIPSIS': ' ...',
25
+ 'EVENT': 'Event',
26
+ 'EXIT': 'Exit',
27
+ 'HELP': 'Help',
28
+ 'INCREMENT_ARROW': '▲',
29
+ 'LOGS': 'Logs',
30
+ 'NEW': 'New',
31
+ 'NEXT': 'Next',
32
+ 'NO': 'No',
33
+ 'NO_SUCH_FILE': 'no such file or directory',
34
+ 'OK': 'OK',
35
+ 'OPEN': 'Open',
36
+ 'PAUSE': 'Pause',
37
+ 'PREFERENCES': 'Preferences',
38
+ 'PREVIOUS': 'Prev',
39
+ 'PROCESS': 'Process',
40
+ 'QUIT': 'Quit',
41
+ 'REDO': 'Redo',
42
+ 'REFRESH': 'Refresh',
43
+ 'REPORT': 'Report',
44
+ 'RENAME': 'Rename',
45
+ 'RESET': 'Reset',
46
+ 'RESTORE': 'Restore',
47
+ 'REVERT': 'Revert',
48
+ 'RUN': 'Run',
49
+ 'SAVE': 'Save',
50
+ 'SCRIPT': 'Script',
51
+ 'SAVE_PDF': 'Save as PDF',
52
+ 'SEARCH': 'Search',
53
+ 'SEND': 'Send',
54
+ 'START': 'Start',
55
+ 'UPDATE': 'Update',
56
+ 'UPGRADE': 'Upgrade',
57
+ 'USE': 'Use',
58
+ 'WINDOWS': 'Windows',
59
+ 'YES': 'Yes',
60
+ }
61
+
62
+ strings = copy(psi_strings)
63
+
64
+
65
+ class Text():
66
+ """Combines package level and psiutils strings."""
67
+ def __init__(self) -> None:
68
+ """
69
+ Initialise the object with attributes based on the key-value pairs
70
+ in the `strings` dictionary.
71
+
72
+ Args:
73
+ self: The instance of the class.
74
+
75
+ Returns:
76
+ None
77
+ """
78
+ self.strings = strings
79
+ for key, string in strings.items():
80
+ setattr(self, key, string)
81
+
82
+ # Optionally display contents of `strings`
83
+ def display(self, compare: dict = None) -> None:
84
+ """Print out contents of `strings` and compare if
85
+ a dict of strings is provided."""
86
+ duplicates = 0
87
+ overrides = 0
88
+ app_strings = 0
89
+ if not compare:
90
+ compare = {}
91
+
92
+ for key, item in compare.items():
93
+ if key not in strings:
94
+ strings[key] = item
95
+
96
+ for item in sorted(list(strings)):
97
+ output = f'{item:.<20} {strings[item]}'
98
+ if item in compare:
99
+ if strings[item] != compare[item]:
100
+ output = (f'{output}, {compare[item]} '
101
+ f'<{"-"*10} //override//')
102
+ overrides += 1
103
+ elif item not in psi_strings:
104
+ output = f'{output} <{"="*10} //app string//'
105
+ app_strings += 1
106
+ else:
107
+ output = f'{output} <{"="*10} //duplicate//'
108
+ duplicates += 1
109
+ print(output)
110
+
111
+ if duplicates:
112
+ print()
113
+ print(f'{duplicates} duplicates found')
114
+
115
+ if overrides:
116
+ print()
117
+ print(f'{overrides} overrides found')
118
+
119
+ if app_strings:
120
+ print()
121
+ print(f'{app_strings} application strings found')
@@ -1,4 +1,5 @@
1
1
  """Common methods for psiutils."""
2
+ import sys
2
3
  from pathlib import Path
3
4
  import tkinter as tk
4
5
  import ctypes
@@ -6,13 +7,12 @@ from typing import Any
6
7
  import platform
7
8
 
8
9
  from psiconfig import TomlConfig
9
- import psiutils.text as txt
10
-
11
- # Exposes psi_logger to other applications
12
10
  from psiutils._logger import psi_logger
13
11
  from psiutils._notify import _notify as notify
12
+ from psiutils.constants import DEFAULT_GEOMETRY
13
+ from psiutils.text import Text
14
14
 
15
- DEFAULT_GEOMETRY = '500x400'
15
+ txt = Text()
16
16
 
17
17
 
18
18
  def display_icon(root: tk.Tk, icon_file_path: str,
@@ -28,6 +28,16 @@ def display_icon(root: tk.Tk, icon_file_path: str,
28
28
  print(f'Cannot find icon file: {icon_file_path}')
29
29
 
30
30
 
31
+ def resource_path(base: Path, relative_path: Path):
32
+ """ Get absolute path to resource, works for dev and for PyInstaller."""
33
+ try:
34
+ # PyInstaller creates a temp folder and stores path in _MEIPASS
35
+ base_path = sys._MEIPASS
36
+ except Exception:
37
+ base_path = Path(base).parent
38
+ return Path(base_path, relative_path)
39
+
40
+
31
41
  class Enum():
32
42
  def __init__(self, values: dict) -> None:
33
43
  self.values = invert(values)
@@ -75,6 +85,7 @@ def enable_frame(parent: tk.Frame, enable: bool = True) -> None:
75
85
  else:
76
86
  child.configure(state=state)
77
87
 
88
+
78
89
  def geometry(config: TomlConfig, file: Path, default: str = '') -> str:
79
90
  if not default:
80
91
  default = DEFAULT_GEOMETRY
@@ -6,6 +6,7 @@ import contextlib
6
6
 
7
7
  from .constants import PAD, COLOURS
8
8
  from ._about_frame import AboutFrame
9
+ from psiutils._date_picker import DatePicker, TimePicker
9
10
 
10
11
  HAND = 'hand2'
11
12
  DIM_TEXT = '#555'
@@ -23,16 +24,16 @@ class PsiText(tk.Text):
23
24
  tk.Text.__init__(self, *args, **kwargs)
24
25
 
25
26
  # create a proxy for the underlying widget
26
- self._orig = f"{self._w}_orig"
27
- self.tk.call("rename", self._w, self._orig)
27
+ self._orig = f'{self._w}_orig'
28
+ self.tk.call('rename', self._w, self._orig)
28
29
  self.tk.createcommand(self._w, self._proxy)
29
30
 
30
31
  def _proxy(self, command, *args):
31
32
  cmd = (self._orig, command) + args
32
33
  result = self.tk.call(cmd)
33
34
 
34
- if command in ("insert", "delete", "replace"):
35
- self.event_generate("<<TextModified>>")
35
+ if command in ('insert', 'delete', 'replace'):
36
+ self.event_generate('<<TextModified>>')
36
37
 
37
38
  return result
38
39
 
@@ -62,6 +63,7 @@ def get_styles() -> ttk.Style:
62
63
 
63
64
  style.configure('red-bg.TEntry', fieldbackground='red')
64
65
  style.configure('green-bg.TEntry', fieldbackground='green')
66
+ style.configure('blue-bg.TEntry', fieldbackground='blue')
65
67
  style.configure('orange-bg.TEntry', fieldbackground='orange')
66
68
  style.configure(
67
69
  'pale-umber-bg.TEntry', fieldbackground=COLOURS['pale-umber'])
@@ -71,6 +73,7 @@ def get_styles() -> ttk.Style:
71
73
  # Entries - foreground
72
74
  style.configure('red-fg.TEntry', foreground='red')
73
75
  style.configure('green-fg.TEntry', foreground='green')
76
+ style.configure('blue-fg.TEntry', foreground='blue')
74
77
  style.configure('orange-fg.TEntry', foreground='orange')
75
78
 
76
79
  # Frames
@@ -83,6 +86,8 @@ def get_styles() -> ttk.Style:
83
86
  # radiobuttons
84
87
  style.configure('red-fg.TRadiobutton', foreground='red')
85
88
  style.configure('green-fg.TRadiobutton', foreground='green')
89
+ style.configure('blue-fg.TRadiobutton', foreground='blue')
90
+ style.configure('orange-fg.TRadiobutton', foreground='orange')
86
91
 
87
92
  # Tree view
88
93
  style.map('Treeview',
@@ -345,7 +350,7 @@ class Tooltip:
345
350
 
346
351
  x, y = tip_pos_calculator(widget, label)
347
352
 
348
- self.tw.wm_geometry("+%d+%d" % (x, y+self.vertical_offset))
353
+ self.tw.wm_geometry('+%d+%d' % (x, y+self.vertical_offset))
349
354
  # self.tw.wm_geometry(f'{x}x{y}')
350
355
 
351
356
  def hide(self):
@@ -1,15 +0,0 @@
1
- # Dialog Status
2
- YES = True
3
- NO = False
4
- CANCEL = None
5
- NULL = 0
6
- EXIT = 1
7
- OK = 2
8
- UPDATED = 3
9
- ERROR = 4
10
-
11
- # Modes
12
- VIEW = 0
13
- NEW = 1
14
- EDIT = 2
15
- DELETE = 3
@@ -1,11 +0,0 @@
1
-
2
- import gi
3
- gi.require_version('Gio', '2.0')
4
- gi.require_version('GioUnix', '2.0')
5
- gi.require_version('Notify', '0.7')
6
- from gi.repository import Notify
7
-
8
-
9
- def _notify(title: str, message: str) -> None:
10
- Notify.init(title)
11
- Notify.Notification.new(message).show()
@@ -1 +0,0 @@
1
- __version__ = '0.2.6'
@@ -1,65 +0,0 @@
1
- """Constants for the tkinter psiutils."""
2
- from .utilities import invert
3
- from .known_paths import get_documents_dir, get_downloads_dir
4
-
5
- DIALOG_STATUS: dict = {
6
- 'yes': True,
7
- 'no': False,
8
- 'cancel': None,
9
- 'null': 0,
10
- 'undefined': 0,
11
- 'exit': 1,
12
- 'ok': 2,
13
- 'updated': 3,
14
- 'error': 4,
15
- }
16
- DIALOG_STATUS = invert(DIALOG_STATUS)
17
-
18
- MODES: dict[int, str] | dict[str, int] = {
19
- 0: 'view',
20
- 1: 'new',
21
- 2: 'edit',
22
- 3: 'delete'
23
- }
24
- MODES = invert(MODES)
25
-
26
- # GUI
27
- PAD = 5
28
- PADR = (0, PAD)
29
- PADL = (PAD, 0)
30
- PADT = (PAD, 0)
31
- PADB = (0, PAD)
32
- LARGE_FONT = ('Arial', 16)
33
- BOLD_FONT = ('Arial', 12, 'bold')
34
-
35
- # Colours
36
- COLOURS = {
37
- 'pale-umber': '#ffcfa0',
38
- 'pale-red': '#ff9999',
39
- 'pale-grey': '#e8e8e8'
40
- }
41
-
42
- CSV_FILE_TYPES = (
43
- ('csv files', '*.csv'),
44
- ('All files', '*.*')
45
- )
46
-
47
- TXT_FILE_TYPES = (
48
- ('csv files', '*.txt'),
49
- ('All files', '*.*')
50
- )
51
-
52
- XML_FILE_TYPES = (
53
- ('xml files', '*.xml'),
54
- ('All files', '*.*')
55
- )
56
-
57
-
58
- class Pad():
59
- W = (0, PAD)
60
- E = (PAD, 0)
61
- N = (PAD, 0)
62
- S = (0, PAD)
63
-
64
- DOCUMENTS_DIR = get_documents_dir()
65
- DOWNLOADS_DIR = get_downloads_dir()
@@ -1,46 +0,0 @@
1
- BUILD = 'Build'
2
- CANCEL = 'Cancel'
3
- CHECK = 'Check'
4
- CLEAR = 'Clear'
5
- CLOSE = 'Close'
6
- CODE = 'Code'
7
- COMPARE = 'Compare'
8
- CONFIG = 'Config'
9
- COPY = 'Copy'
10
- DELETE = 'Delete'
11
- DELETE_THESE_ITEMS = 'Are you sure you want to delete these item(s)?'
12
- DIFF = 'Diff'
13
- DONE = 'Done'
14
- EDIT = 'Edit'
15
- EVENT = 'Event'
16
- EXIT = 'Exit'
17
- HELP = 'Help'
18
- NEW = 'New'
19
- NEXT = 'Next'
20
- NO = 'No'
21
- NO_SUCH_FILE = 'no such file or directory'
22
- OK = 'OK'
23
- OPEN = 'Open'
24
- PAUSE = 'Pause'
25
- PREFERENCES = 'Preferences'
26
- PREVIOUS = 'Prev'
27
- PROCESS = 'Process'
28
- QUIT = 'Quit'
29
- REDO = 'Redo'
30
- REFRESH = 'Refresh'
31
- REPORT = 'Report'
32
- RENAME = 'Rename'
33
- RESET = 'Reset'
34
- REVERT = 'Revert'
35
- RUN = 'Run'
36
- SAVE = 'Save'
37
- SCRIPT = 'Script'
38
- SAVE_PDF = 'Save as PDF'
39
- SEARCH = 'Search'
40
- SEND = 'Send'
41
- START = 'Start'
42
- UPDATE = 'Update'
43
- UPGRADE = 'Upgrade'
44
- USE = 'Use'
45
- WINDOWS = 'Windows'
46
- YES = 'Yes'
File without changes