imagelist 1.2.0__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.
- imagelist/__init__.py +10 -0
- imagelist/__init__.pyi +3 -0
- imagelist/imagelist.py +320 -0
- imagelist/imagelist.pyi +33 -0
- imagelist/py.typed +0 -0
- imagelist-1.2.0.dist-info/METADATA +284 -0
- imagelist-1.2.0.dist-info/RECORD +10 -0
- imagelist-1.2.0.dist-info/WHEEL +5 -0
- imagelist-1.2.0.dist-info/licenses/LICENSE +19 -0
- imagelist-1.2.0.dist-info/top_level.txt +1 -0
imagelist/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""A managed collection of PhotoImages for use with Tkinter widgets.
|
|
2
|
+
|
|
3
|
+
This package provides the following class definition:
|
|
4
|
+
|
|
5
|
+
* ImageList - A managed collection of PhotoImages for use with Tkinter widgets
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = '1.2.0'
|
|
9
|
+
|
|
10
|
+
from .imagelist import ImageList
|
imagelist/__init__.pyi
ADDED
imagelist/imagelist.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""A managed collection of PhotoImages for use with Tkinter widgets.
|
|
2
|
+
|
|
3
|
+
This module provides the following class definition:
|
|
4
|
+
|
|
5
|
+
* ImageList - A managed collection of PhotoImages for use with Tkinter widgets
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = '1.2.0'
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from warnings import warn
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Tuple, List, Union, Optional
|
|
14
|
+
from PIL.ImageTk import PhotoImage
|
|
15
|
+
from PIL import Image, UnidentifiedImageError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ImageList:
|
|
19
|
+
"""A managed collection of PhotoImages for use with Tkinter widgets."""
|
|
20
|
+
|
|
21
|
+
class Grayed:
|
|
22
|
+
"""A managed collection of grayed PhotoImages."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, parent: 'ImageList'):
|
|
25
|
+
"""Construct and initialize the collection."""
|
|
26
|
+
self._image_list = parent
|
|
27
|
+
self._grayed: List[PhotoImage] = parent._local.grayed
|
|
28
|
+
|
|
29
|
+
def __len__(self) -> int:
|
|
30
|
+
"""Get the total number of images currently in the collection."""
|
|
31
|
+
return len(self._grayed)
|
|
32
|
+
|
|
33
|
+
def __iter__(self) -> Any:
|
|
34
|
+
"""Make the Grayed class an iterable collection."""
|
|
35
|
+
return (image for image in self._grayed)
|
|
36
|
+
|
|
37
|
+
def __getitem__(self, item: Union[int, str]) -> Any:
|
|
38
|
+
"""Get the grayed image with the given index value or key name."""
|
|
39
|
+
result: Any = self._image_list.blank_image
|
|
40
|
+
valid, index = self._image_list._find_index(item)
|
|
41
|
+
if valid:
|
|
42
|
+
result = self._grayed[index]
|
|
43
|
+
else:
|
|
44
|
+
self._image_list._warn_item(item)
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class _Properties:
|
|
49
|
+
"""The ImageList properties."""
|
|
50
|
+
|
|
51
|
+
resource_folder: str
|
|
52
|
+
image_size: Tuple[int, int]
|
|
53
|
+
images: List[PhotoImage] = field(default_factory=list)
|
|
54
|
+
grayed: List[PhotoImage] = field(default_factory=list)
|
|
55
|
+
keys: List[str] = field(default_factory=list)
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
resource_folder: str = '',
|
|
60
|
+
image_size: Tuple[int, int] = (16, 16),
|
|
61
|
+
auto_load: bool = False,
|
|
62
|
+
):
|
|
63
|
+
"""Construct and initialize the PhotoImage collection.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
resource_folder : str
|
|
68
|
+
The path of the resource file folder for image files, default = ''
|
|
69
|
+
image_size : tuple[int, int]
|
|
70
|
+
The size of the images in the collection, default = (16, 16) pixels
|
|
71
|
+
auto_load : bool
|
|
72
|
+
Automatically load all the resource folder's image files if True
|
|
73
|
+
"""
|
|
74
|
+
width = max(1, min(image_size[0], 256))
|
|
75
|
+
height = max(1, min(image_size[1], 256))
|
|
76
|
+
image_size = (width, height)
|
|
77
|
+
self._local = self._Properties(resource_folder, image_size)
|
|
78
|
+
self._blank_image = PhotoImage(Image.new('RGBA', image_size, 0))
|
|
79
|
+
if auto_load:
|
|
80
|
+
self._verbose = False
|
|
81
|
+
for file in sorted(os.listdir(resource_folder), key=str.lower):
|
|
82
|
+
index = file.rfind('.')
|
|
83
|
+
self.add(file, file[:index])
|
|
84
|
+
self._verbose = True
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def blank_image(self) -> Any:
|
|
88
|
+
"""Get a blank (or transparent) image."""
|
|
89
|
+
return self._blank_image
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def grayed(self) -> 'ImageList.Grayed':
|
|
93
|
+
"""Get the grayed PhotoImage collection."""
|
|
94
|
+
return self.Grayed(self)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def image_size(self) -> Tuple[int, int]:
|
|
98
|
+
"""Get the size of the images in the collection."""
|
|
99
|
+
return self._local.image_size
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def keys(self) -> List[str]:
|
|
103
|
+
"""Get a list of the key names currently assigned to the images."""
|
|
104
|
+
return list(self._local.keys)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def resource_folder(self) -> str:
|
|
108
|
+
"""Get/Set the path of the resource file folder for image files."""
|
|
109
|
+
return self._local.resource_folder
|
|
110
|
+
|
|
111
|
+
@resource_folder.setter
|
|
112
|
+
def resource_folder(self, path: str) -> None:
|
|
113
|
+
"""Get/Set the path of the resource file folder for image files."""
|
|
114
|
+
self._local.resource_folder = path
|
|
115
|
+
|
|
116
|
+
def __len__(self) -> int:
|
|
117
|
+
"""Get the total number of images currently in the collection."""
|
|
118
|
+
return len(self._local.images)
|
|
119
|
+
|
|
120
|
+
def __iter__(self) -> Any:
|
|
121
|
+
"""Make the ImageList class an iterable collection."""
|
|
122
|
+
return (image for image in self._local.images)
|
|
123
|
+
|
|
124
|
+
def __getitem__(self, item: Union[int, str, slice]) -> Any:
|
|
125
|
+
"""Get the image with the specified index value or key name."""
|
|
126
|
+
image_list = ImageList(self.resource_folder, self.image_size)
|
|
127
|
+
image = self._blank_image
|
|
128
|
+
if isinstance(item, slice):
|
|
129
|
+
try:
|
|
130
|
+
grayed = self._local.grayed[item]
|
|
131
|
+
images = self._local.images[item]
|
|
132
|
+
keys = self._local.keys[item]
|
|
133
|
+
for i, name in enumerate(keys):
|
|
134
|
+
image_list.update(grayed[i], images[i], name)
|
|
135
|
+
except TypeError:
|
|
136
|
+
warn("The slice indices must be integer values!", stacklevel=2)
|
|
137
|
+
else:
|
|
138
|
+
valid, index = self._find_index(item)
|
|
139
|
+
if valid:
|
|
140
|
+
image = self._local.images[index]
|
|
141
|
+
else:
|
|
142
|
+
self._warn_item(item)
|
|
143
|
+
return image_list if isinstance(item, slice) else image
|
|
144
|
+
|
|
145
|
+
def add(self, image_file: str, key_name: Optional[str] = None) -> bool:
|
|
146
|
+
"""Add an image with an optional key name to the end of the collection.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
image_file : str
|
|
151
|
+
The image file to add to the collection
|
|
152
|
+
key_name : str
|
|
153
|
+
The optional key name of the image (not case-sensitive)
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
bool
|
|
158
|
+
True if the image was successfully added, False otherwise
|
|
159
|
+
"""
|
|
160
|
+
success, image, grayed = self._get_image(image_file)
|
|
161
|
+
if success:
|
|
162
|
+
self._local.keys.append('' if key_name is None else key_name)
|
|
163
|
+
self._local.images.append(image)
|
|
164
|
+
self._local.grayed.append(grayed)
|
|
165
|
+
return success
|
|
166
|
+
|
|
167
|
+
def clear(self) -> None:
|
|
168
|
+
"""Remove all the images and keys from the collection."""
|
|
169
|
+
self._local.keys.clear()
|
|
170
|
+
self._local.images.clear()
|
|
171
|
+
self._local.grayed.clear()
|
|
172
|
+
|
|
173
|
+
def contains_key(self, name: str) -> bool:
|
|
174
|
+
"""Determine if the collection has an image with the specified key.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
name : str
|
|
179
|
+
The specified key name of the image (not case-sensitive)
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
bool
|
|
184
|
+
True if the collection contains the key name, False otherwise
|
|
185
|
+
"""
|
|
186
|
+
return self._find_index(name)[0]
|
|
187
|
+
|
|
188
|
+
def extend(self, image_list: 'ImageList') -> bool:
|
|
189
|
+
"""Add a PhotoImage collection to the end of the current collection.
|
|
190
|
+
|
|
191
|
+
The image_size property of the PhotoImage collection must match that of
|
|
192
|
+
the current collection in order to be successfully added.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
image_list : ImageList
|
|
197
|
+
The PhotoImage collection.
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
bool
|
|
202
|
+
True if the PhotoImage collection was added, False otherwise
|
|
203
|
+
"""
|
|
204
|
+
valid = image_list.image_size == self.image_size
|
|
205
|
+
if valid:
|
|
206
|
+
for i, name in enumerate(image_list.keys):
|
|
207
|
+
self.update(image_list.grayed[i], image_list[i], name)
|
|
208
|
+
else:
|
|
209
|
+
warn('The PhotoImage sizes do not Match!', stacklevel=2)
|
|
210
|
+
return valid
|
|
211
|
+
|
|
212
|
+
def index_of_key(self, name: str) -> int:
|
|
213
|
+
"""Return the zero-based index of the image with the specified key.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
name : str
|
|
218
|
+
The specified key name of the image (not case-sensitive)
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
int
|
|
223
|
+
The index of the first occurrence of the key name, -1 otherwise
|
|
224
|
+
"""
|
|
225
|
+
index = -1
|
|
226
|
+
if name:
|
|
227
|
+
key_lower = name.lower()
|
|
228
|
+
for i, key in enumerate(self._local.keys):
|
|
229
|
+
if key.lower() == key_lower:
|
|
230
|
+
index = i
|
|
231
|
+
break
|
|
232
|
+
return index
|
|
233
|
+
|
|
234
|
+
def remove_at(self, index: int) -> None:
|
|
235
|
+
"""Remove an image from the collection at the specified index.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
index : int
|
|
240
|
+
The zero-based index value of the image in the collection
|
|
241
|
+
"""
|
|
242
|
+
if self._find_index(index)[0]:
|
|
243
|
+
self._local.keys.pop(index)
|
|
244
|
+
self._local.images.pop(index)
|
|
245
|
+
self._local.grayed.pop(index)
|
|
246
|
+
|
|
247
|
+
def remove_by_key(self, name: str) -> None:
|
|
248
|
+
"""Remove the image with the specified key name from the collection.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
name : str
|
|
253
|
+
The specified key name of the image (not case-sensitive)
|
|
254
|
+
"""
|
|
255
|
+
valid, index = self._find_index(name)
|
|
256
|
+
if valid:
|
|
257
|
+
self.remove_at(index)
|
|
258
|
+
|
|
259
|
+
def set_key_name(self, index: int, name: str) -> None:
|
|
260
|
+
"""Set the key name for an image in the collection.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
index : int
|
|
265
|
+
The zero-based index value of the image in the collection
|
|
266
|
+
name : str
|
|
267
|
+
The name to be set as the image's key name (not case-sensitive)
|
|
268
|
+
"""
|
|
269
|
+
if self._find_index(index)[0]:
|
|
270
|
+
self._local.keys[index] = name
|
|
271
|
+
|
|
272
|
+
def update(self, grayed: PhotoImage, image: PhotoImage, key: str) -> None:
|
|
273
|
+
"""Update the grayed, image, and key lists."""
|
|
274
|
+
self._local.grayed.append(grayed)
|
|
275
|
+
self._local.images.append(image)
|
|
276
|
+
self._local.keys.append(key)
|
|
277
|
+
|
|
278
|
+
def _find_index(self, item: Any) -> Tuple[bool, int]:
|
|
279
|
+
"""Find the existence status and index value of the specified item."""
|
|
280
|
+
index = -1
|
|
281
|
+
count = len(self._local.images)
|
|
282
|
+
if isinstance(item, int):
|
|
283
|
+
index = item if item >= 0 else (count + item)
|
|
284
|
+
elif isinstance(item, str):
|
|
285
|
+
index = self.index_of_key(item)
|
|
286
|
+
return 0 <= index < count, index
|
|
287
|
+
|
|
288
|
+
def _get_image(self, filename: str) -> Tuple[bool, PhotoImage, PhotoImage]:
|
|
289
|
+
"""Try to obtain an image from the specified source."""
|
|
290
|
+
success, image = False, Image.new('RGBA', self.image_size, 0)
|
|
291
|
+
file = os.path.join(self.resource_folder, filename)
|
|
292
|
+
index = file.rfind('.')
|
|
293
|
+
if index > 0:
|
|
294
|
+
if not os.path.isfile(file): # Try an upper case extension
|
|
295
|
+
file = file[:index] + file[index:].upper()
|
|
296
|
+
if not os.path.isfile(file): # Try a lower case extension
|
|
297
|
+
file = file[:index] + file[index:].lower()
|
|
298
|
+
if os.path.isfile(file):
|
|
299
|
+
try:
|
|
300
|
+
image = Image.open(file).convert('RGBA')
|
|
301
|
+
success = True
|
|
302
|
+
except UnidentifiedImageError:
|
|
303
|
+
if self._verbose:
|
|
304
|
+
warn(f"'{filename}' is not an Image File!", stacklevel=3)
|
|
305
|
+
else:
|
|
306
|
+
warn(f"The file: '{file}' does not Exist!", stacklevel=3)
|
|
307
|
+
|
|
308
|
+
if image.size != self.image_size:
|
|
309
|
+
image = image.resize(self.image_size, Image.Resampling.LANCZOS)
|
|
310
|
+
|
|
311
|
+
red, green, blue, alpha = image.split()
|
|
312
|
+
grayed_alpha = alpha.point(lambda x: x * 0.35)
|
|
313
|
+
grayed = Image.merge('RGBA', (red, green, blue, grayed_alpha))
|
|
314
|
+
return success, PhotoImage(image), PhotoImage(grayed)
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def _warn_item(item: Union[int, str]) -> None:
|
|
318
|
+
"""Send a warning about an invalid index or key value."""
|
|
319
|
+
label = 'index' if isinstance(item, (int, float)) else 'key'
|
|
320
|
+
warn(f"'{item}' is not a valid {label} value!", stacklevel=3)
|
imagelist/imagelist.pyi
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
__version__: str
|
|
4
|
+
|
|
5
|
+
class ImageList:
|
|
6
|
+
class Grayed:
|
|
7
|
+
def __len__(self) -> int: ...
|
|
8
|
+
def __iter__(self) -> Any: ...
|
|
9
|
+
def __getitem__(self, item: int | str) -> Any: ...
|
|
10
|
+
def __init__(self, resource_folder: str = '', image_size: tuple[int, int] = (16, 16), auto_load : bool = False) -> None: ...
|
|
11
|
+
@property
|
|
12
|
+
def blank_image(self) -> Any: ...
|
|
13
|
+
@property
|
|
14
|
+
def grayed(self) -> ImageList.Grayed: ...
|
|
15
|
+
@property
|
|
16
|
+
def image_size(self) -> tuple[int, int]: ...
|
|
17
|
+
@property
|
|
18
|
+
def keys(self) -> list[str]: ...
|
|
19
|
+
@property
|
|
20
|
+
def resource_folder(self) -> str: ...
|
|
21
|
+
@resource_folder.setter
|
|
22
|
+
def resource_folder(self, path: str) -> None: ...
|
|
23
|
+
def __len__(self) -> int: ...
|
|
24
|
+
def __iter__(self) -> Any: ...
|
|
25
|
+
def __getitem__(self, item: int | str | slice) -> Any: ...
|
|
26
|
+
def add(self, image_file: str, key_name: str | None = None) -> bool: ...
|
|
27
|
+
def clear(self) -> None: ...
|
|
28
|
+
def contains_key(self, name: str) -> bool: ...
|
|
29
|
+
def extend(self, image_list: ImageList) -> bool: ...
|
|
30
|
+
def index_of_key(self, name: str) -> int: ...
|
|
31
|
+
def remove_at(self, index: int) -> None: ...
|
|
32
|
+
def remove_by_key(self, name: str) -> None: ...
|
|
33
|
+
def set_key_name(self, index: int, name: str) -> None: ...
|
imagelist/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: imagelist
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: A managed collection of PhotoImages for use with Tkinter widgets.
|
|
5
|
+
Author-email: John Bolkcom <johnbolk6502@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/johnbolk/imagelist.git
|
|
8
|
+
Keywords: Tkinter,PhotoImage,PIL,macOS,Icons
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: pillow>=9.5.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
**A managed collection of PhotoImages for use with Tkinter widgets.**
|
|
19
|
+
|
|
20
|
+
This package furnishes a convenient way of providing PhotoImages to Tkinter widgets that display images, such as Buttons, Labels, and Menus.
|
|
21
|
+
|
|
22
|
+
The **ImageList** class incorporates the capabilities of the **Python Imaging Library (PIL)** for both loading images from a wide variety of commonly used image file formats, and for resizing and reformating the original images into Tkinter compatible PhotoImages. Regardless of the original source image dimensions, every image that is added to the collection is resized ( if needed ) to the **image_size** that was specified when the **ImageList** was created. In addition to providing PhotoImages of uniform size, the **ImageList** class also provides image object persistence for Tkinter widgets by maintaining a reference for each PhotoImage in the collection.
|
|
23
|
+
|
|
24
|
+
**Attention macOS Users :** When working with a Tkinter widget on either a **Windows** or a **Linux** based platform, the widget will display a "grayed" image when the widget's **state** option is set to **'disabled'**. This behavior visually indicates whether the widget is active or inactive. However, when using that same Tkinter widget on a **macOS** computer, the image displayed by the widget is left unchanged when the widget's **state** option is set to **'disabled'**. The **ImageList** class was designed to help resolve this issue by maintaining a corresponding collection of "grayed" PhotoImages that are also created from the image files. By assigning the corresponding "grayed" PhotoImage to the **image** option of a **'disabled'** widget, the widget can visually indicate that it is not active.
|
|
25
|
+
|
|
26
|
+
<div class="page"/>
|
|
27
|
+
|
|
28
|
+
# Overview
|
|
29
|
+
|
|
30
|
+
This package provides the following class definition **:**
|
|
31
|
+
|
|
32
|
+
* **ImageList -** A managed collection of PhotoImages for use by Tkinter widgets
|
|
33
|
+
|
|
34
|
+
**Note :** A *top-level* or *root* window must be in existence prior to creating an instance of the **ImageList** class.
|
|
35
|
+
|
|
36
|
+
The **ImageList** class provides a programming interface that is similar to that of a Python **list** object. The PhotoImage elements in an **ImageList** collection are ordered and indexed, and a specific PhotoImage can be accessed by referring to its index number. Both positive and negative index values are supported. The **ImageList** collection will issue a warning message and return a blank PhotoImage when referenced with an invalid index value. The **len( )** function can is used to determine the total number of PhotoImage elements in the **ImageList** collection.
|
|
37
|
+
|
|
38
|
+
For example:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
# Create a collection of PhotoImages that are all 16 x 16 pixels in size
|
|
42
|
+
|
|
43
|
+
image_list = ImageList() # The default image size is 16 x 16 pixels
|
|
44
|
+
image_list.add('image_file_0') # Add the first image from image_file_0
|
|
45
|
+
image_list.add('image_file_1') # Add the next image from image_file_1
|
|
46
|
+
# Continue to add more images to the collection
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
image_count = len(image_list) # Determine the total number of PhotoImages
|
|
50
|
+
|
|
51
|
+
first_image = image_list[0] # Get the first PhotoImage in the collection
|
|
52
|
+
second_image = image_list[1] # Get the second PhotoImage in the collection
|
|
53
|
+
|
|
54
|
+
last_image = image_list[-1] # Get the last PhotoImage in the collection
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A specific PhotoImage element in an **ImageList** collection can also be accessed by referencing its (optional) key name. An image's key name can be specified when the image is added to the collection, or it can be assigned at a later time by using the **set_key_name( index, name )** method. Unlike a dictionary, the **ImageList** collection does not require every element to have an unique key name value, and the key name values are not case sensitive. When an image is added without a specified key name, the collection will assign an empty string as that image's key name value. When accessing a PhotoImage in the collection by a key name value, the first PhotoImage with a matching key name value is returned. The **ImageList** collection will issue a warning message and return a blank PhotoImage when referenced with either an empty string or a non-matching key name value. The **keys** property returns a list of the key names that are currently assigned to the PhotoImages in the collection.
|
|
58
|
+
|
|
59
|
+
<div class="page"/>
|
|
60
|
+
|
|
61
|
+
The following example shows the use of key name values:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
image_list = ImageList()
|
|
65
|
+
image_list.add('image_file_0', 'key_name_0') # Specify image_0's key name
|
|
66
|
+
image_list.add('image_file_1', 'key_name_1') # Specify image_1's key name
|
|
67
|
+
# Continue to add more images to the collection
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
first_image = image_list['key_name_0'] # Get the first PhotoImage
|
|
71
|
+
second_image = image_list['key_name_1'] # Get the second PhotoImage
|
|
72
|
+
|
|
73
|
+
image_list.set_key_name(0, 'new_name') # Change the first PhotoImage's key name
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The **ImageList** class's **grayed** property provides access to the "grayed" PhotoImage collection. Each PhotoImage element in the **ImageList** collection has a corresponding "grayed" PhotoImage in the **ImageList.Grayed** collection. These paired images share the same index number and key name values.
|
|
77
|
+
|
|
78
|
+
This example illustrates how the **ImageList** can be used to provide a "grayed" image to a Tkinter Button widget when it is made inactive:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
import tkinter as tk
|
|
82
|
+
import platform
|
|
83
|
+
|
|
84
|
+
image_list = ImageList()
|
|
85
|
+
image_list.add('image_file', 'key_name') # Add an image file and a key name
|
|
86
|
+
|
|
87
|
+
button = tk.Button(parent, image=image_list['key_name'], command=command)
|
|
88
|
+
|
|
89
|
+
# Make the button inactive and display a "grayed" image
|
|
90
|
+
button.configure(state='disabled')
|
|
91
|
+
if platform.system() == 'Darwin': # Check for the macOS platform
|
|
92
|
+
button.configure(image=image_list.grayed['key_name'])
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
<div class="page"/>
|
|
96
|
+
|
|
97
|
+
# API Documentation
|
|
98
|
+
|
|
99
|
+
## ImageList
|
|
100
|
+
|
|
101
|
+
### ImageList( resource_folder=' ', image_size=( 16, 16 ), auto_load=False )
|
|
102
|
+
|
|
103
|
+
Constructs and initializes the PhotoImage collection.
|
|
104
|
+
|
|
105
|
+
* **resource_folder: str -** The path of the resource file folder for the image files, **default = ' '.**
|
|
106
|
+
|
|
107
|
+
* **image_size: tuple[ int, int ] -** The size ( width, height ) of the PhotoImages, **default = ( 16, 16 ) pixels.**
|
|
108
|
+
|
|
109
|
+
* **auto_load: bool -** Automatically load all the resource folder's image files if True, **default = False.**
|
|
110
|
+
|
|
111
|
+
The **resource_folder** string is used internally by the **add( image_file, key_name=None)** method to construct the full path name when adding an image to the collection.
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
full_path_name = os.path.join(resource_folder, image_file)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
When the **auto_load** parameter's value is set **True**, all the valid image files in the specified **resource_folder** will be identified and automatically added to the collection. The image files are added to the collection in alphabetical order. The **key_name** value assigned to each added image will be the image file's *stem* or *base* name ( the image filename without its extension ).
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
extension = filename.rfind('.')
|
|
121
|
+
key_name = filename[:extension]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Properties
|
|
125
|
+
|
|
126
|
+
* **blank_image: PhotoImage -** A blank ( or transparent ) PhotoImage. ( readonly )
|
|
127
|
+
|
|
128
|
+
* **grayed: ImageList.Grayed -** A managed collection of grayed PhotoImages. ( readonly )
|
|
129
|
+
|
|
130
|
+
* **image_size: tuple[ int, int ] -** The size ( width, height ) of the PhotoImages in the collection. ( readonly )
|
|
131
|
+
|
|
132
|
+
* **keys: list[ str ] -** A list of the key names currently assigned to the PhotoImages. ( readonly )
|
|
133
|
+
|
|
134
|
+
* **resource_folder: str -** The path of the resource file folder for the image files. ( read / write )
|
|
135
|
+
|
|
136
|
+
<div class="page"/>
|
|
137
|
+
|
|
138
|
+
### Methods
|
|
139
|
+
|
|
140
|
+
* **add( image_file, key_name=None ) -> bool :** Add an image with an optional key name to the end of the collection. The full path name of the image file is constructed from the **resource_folder** property value and the **image_file** parameter value.
|
|
141
|
+
* **image_file: str -** The image file to add to the collection.
|
|
142
|
+
* **key_name: str | None -** The optional key name of the image (not case-sensitive).
|
|
143
|
+
|
|
144
|
+
**Returns : True** if the image was successfully added **, False** otherwise
|
|
145
|
+
|
|
146
|
+
* **clear( ) :** Remove all the images and key names from the collection.
|
|
147
|
+
|
|
148
|
+
* **contains_key( name ) -> bool :** Determine if the collection has an image with the specified key name.
|
|
149
|
+
* **name: str -** The specified key name of the image (not case-sensitive).
|
|
150
|
+
|
|
151
|
+
**Returns : True** if the collection contains the key name **, False** otherwise.
|
|
152
|
+
|
|
153
|
+
* **extend( image_list ) -> bool :** Add a PhotoImage collection to the end of the current collection. The **image_size** property of the PhotoImage collection must match that of the current collection in order to be successfully added.
|
|
154
|
+
* **image_list: ImageList -** The specified PhotoImage list.
|
|
155
|
+
|
|
156
|
+
**Returns : True** if the PhotoImage collection was successfully added **, False** otherwise.
|
|
157
|
+
|
|
158
|
+
* **index_of_key( name: str ) -> int :** Return the zero-based index of the image with the specified key name.
|
|
159
|
+
* **name: str -** The specified key name of the image (not case-sensitive).
|
|
160
|
+
|
|
161
|
+
**Returns :** The **index** of the first occurrence of the key name **, -1** otherwise.
|
|
162
|
+
|
|
163
|
+
* **remove_at( index: int ) :** Remove an image from the collection at the specified index.
|
|
164
|
+
* **index -** The zero-based index value of the image in the collection
|
|
165
|
+
|
|
166
|
+
* **remove_by_key( name: str ) :** Remove the image with the specified key name from the collection.
|
|
167
|
+
* **name: str -** The specified key name of the image (not case-sensitive).
|
|
168
|
+
|
|
169
|
+
* **set_key_name( index: int, name: str ) :** Set the key name for an image in the collection.
|
|
170
|
+
* **index -** The zero-based index value of the image in the collection.
|
|
171
|
+
* **name -** The name to be set as the image's key name (not case-sensitive).
|
|
172
|
+
|
|
173
|
+
<div class="page"/>
|
|
174
|
+
|
|
175
|
+
# ImageList Usage Examples
|
|
176
|
+
|
|
177
|
+
The source files and image files for these examples are available at the [imagelist GitHub Repository](https://github.com/johnbolk/imagelist).
|
|
178
|
+
|
|
179
|
+
This first example shows how the **ImageList** can be used to provide the "inactive" or "grayed" image for a Tkinter Button widget regardless of the computer's operating system. As explained earlier, when running on a **macOS** computer, a tkinter widget does not display a "grayed" image when that widget's **state** is set to **'disabled'**. In this example, the **ImageList.Grayed** collection is used provide the "grayed" image for the widget when running on a **macOS** platform.
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
import platform
|
|
183
|
+
import tkinter as tk
|
|
184
|
+
from imagelist import ImageList
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class MacButton(tk.Button):
|
|
188
|
+
"""A macOS compatible image button widget."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, parent, image_list, image_id, command):
|
|
191
|
+
"""Create and initialize the macOS compatible image button widget."""
|
|
192
|
+
self._grayed = self._image = image_list[image_id]
|
|
193
|
+
if platform.system() == 'Darwin': # Check for the macOS platform
|
|
194
|
+
self._grayed = image_list.grayed[image_id]
|
|
195
|
+
width, height = image_list.image_size
|
|
196
|
+
super().__init__(parent, image=self._image, command=command)
|
|
197
|
+
self.config(width=width * 1.25, height=height * 1.25)
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def enabled(self):
|
|
201
|
+
"""Get/Set the button enabled status."""
|
|
202
|
+
return self['state'] != tk.DISABLED
|
|
203
|
+
|
|
204
|
+
@enabled.setter
|
|
205
|
+
def enabled(self, enabled):
|
|
206
|
+
"""Get/Set the button enabled status."""
|
|
207
|
+
self['state'] = tk.NORMAL if enabled else tk.DISABLED
|
|
208
|
+
self['image'] = self._image if enabled else self._grayed
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class DemoForm(tk.Frame):
|
|
212
|
+
"""The MacButton demo window."""
|
|
213
|
+
|
|
214
|
+
def __init__(self, master):
|
|
215
|
+
"""Construct the MacButton demo window."""
|
|
216
|
+
super().__init__(master, bd=3, relief='ridge', padx=50, pady=20)
|
|
217
|
+
self.grid()
|
|
218
|
+
|
|
219
|
+
image_list = ImageList(image_size=(48, 48))
|
|
220
|
+
image_list.add('image_folder/printer.png') # Add an image of a printer
|
|
221
|
+
self._button = MacButton(self, image_list, 0, self._on_print)
|
|
222
|
+
self._button.enabled = False
|
|
223
|
+
self._button.grid(pady=20)
|
|
224
|
+
|
|
225
|
+
self._print_enable = tk.IntVar()
|
|
226
|
+
checkbox = tk.Checkbutton(self, text='Enable Print Button')
|
|
227
|
+
checkbox.config(variable=self._print_enable, command=self._on_check)
|
|
228
|
+
checkbox.grid()
|
|
229
|
+
|
|
230
|
+
def _on_check(self):
|
|
231
|
+
"""Event handler for the Checkbutton."""
|
|
232
|
+
self._button.enabled = self._print_enable.get() != 0
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def _on_print():
|
|
236
|
+
"""Event handler for the MacButton."""
|
|
237
|
+
print('*** Print ***')
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
if __name__ == '__main__':
|
|
241
|
+
main_form = DemoForm(tk.Tk())
|
|
242
|
+
main_form.mainloop()
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
<div class="page"/>
|
|
246
|
+
|
|
247
|
+
This next example demonstrates how to use the **auto_load** option to create an instance of an **ImageList** class that automatically adds all the image files located in the specified **'image_folder'** ( 'Help.ico', 'none.ico', 'settings.ico', 'info.png', 'printer.png', 'Stop.png' ). This script creates and displays a group of four image buttons. Each of these buttons is identified by the key name value of it's assigned image. Each button's event handler displays the name of that button when it is clicked.
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
import tkinter as tk
|
|
251
|
+
from imagelist import ImageList
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class DemoForm(tk.Frame):
|
|
255
|
+
"""The image buttons demo window."""
|
|
256
|
+
|
|
257
|
+
def __init__(self, master):
|
|
258
|
+
"""Construct the image buttons demo window."""
|
|
259
|
+
super().__init__(master, bd=3, relief='ridge', padx=20, pady=20)
|
|
260
|
+
self.grid()
|
|
261
|
+
|
|
262
|
+
group = tk.LabelFrame(self, text='Image Buttons')
|
|
263
|
+
image_list = ImageList('image_folder', (32, 32), True)
|
|
264
|
+
image_width, image_height = image_list.image_size
|
|
265
|
+
for i, name in enumerate(['printer', 'settings', 'help', 'stop']):
|
|
266
|
+
|
|
267
|
+
def handler(index=image_list.index_of_key(name)):
|
|
268
|
+
text = '???' if index < 0 else image_list.keys[index].title()
|
|
269
|
+
label.configure(text=f"'{text}' Button")
|
|
270
|
+
|
|
271
|
+
button = tk.Button(group, image=image_list[name], command=handler)
|
|
272
|
+
button.configure(width=image_width + 8, height=image_height + 8)
|
|
273
|
+
button.grid(column=i, row=0, padx=15, pady=(10, 15))
|
|
274
|
+
group.grid(pady=(0, 20))
|
|
275
|
+
|
|
276
|
+
label = tk.Label(self, bd=1, relief='solid', width=20, anchor='center')
|
|
277
|
+
label.configure(text='Click any Button')
|
|
278
|
+
label.grid()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if __name__ == '__main__':
|
|
282
|
+
main_form = DemoForm(tk.Tk())
|
|
283
|
+
main_form.mainloop()
|
|
284
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
imagelist/__init__.py,sha256=8u1llsGAglG8FjfpF2F-awurtnSOeGnNlcYaZadPp4A,275
|
|
2
|
+
imagelist/__init__.pyi,sha256=LX_M_EomaBtZgqlpGqRAeyR2HQizgCJk5IEekZ-F9AY,64
|
|
3
|
+
imagelist/imagelist.py,sha256=OaOM34vRkj4VEjjb7Wr44YkjZuP10l5Cs84V2s7uj_U,11828
|
|
4
|
+
imagelist/imagelist.pyi,sha256=7YMKWHyh6fWaRxoyVBFrJ53HbEzMabh1L9kjuWfw9ik,1270
|
|
5
|
+
imagelist/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
imagelist-1.2.0.dist-info/licenses/LICENSE,sha256=0pnDVPg5EVJIv8x-AdVJQly3642pM2zEi-7hZTcyMyU,1075
|
|
7
|
+
imagelist-1.2.0.dist-info/METADATA,sha256=OcCBck6lyRBFdA40Jg5FZMAeO3ICojOZIHxeppUyYpg,15022
|
|
8
|
+
imagelist-1.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
9
|
+
imagelist-1.2.0.dist-info/top_level.txt,sha256=G2rr6FTO9dOOlCLhEC-lfbbUbWiAXetEcrfjTueX_cU,10
|
|
10
|
+
imagelist-1.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 John Bolkcom
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
imagelist
|