imagelist 1.2.0__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.
@@ -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,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,267 @@
1
+ **A managed collection of PhotoImages for use with Tkinter widgets.**
2
+
3
+ This package furnishes a convenient way of providing PhotoImages to Tkinter widgets that display images, such as Buttons, Labels, and Menus.
4
+
5
+ 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.
6
+
7
+ **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.
8
+
9
+ <div class="page"/>
10
+
11
+ # Overview
12
+
13
+ This package provides the following class definition **:**
14
+
15
+ * **ImageList -** A managed collection of PhotoImages for use by Tkinter widgets
16
+
17
+ **Note :** A *top-level* or *root* window must be in existence prior to creating an instance of the **ImageList** class.
18
+
19
+ 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.
20
+
21
+ For example:
22
+
23
+ ```
24
+ # Create a collection of PhotoImages that are all 16 x 16 pixels in size
25
+
26
+ image_list = ImageList() # The default image size is 16 x 16 pixels
27
+ image_list.add('image_file_0') # Add the first image from image_file_0
28
+ image_list.add('image_file_1') # Add the next image from image_file_1
29
+ # Continue to add more images to the collection
30
+ ...
31
+
32
+ image_count = len(image_list) # Determine the total number of PhotoImages
33
+
34
+ first_image = image_list[0] # Get the first PhotoImage in the collection
35
+ second_image = image_list[1] # Get the second PhotoImage in the collection
36
+
37
+ last_image = image_list[-1] # Get the last PhotoImage in the collection
38
+ ```
39
+
40
+ 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.
41
+
42
+ <div class="page"/>
43
+
44
+ The following example shows the use of key name values:
45
+
46
+ ```
47
+ image_list = ImageList()
48
+ image_list.add('image_file_0', 'key_name_0') # Specify image_0's key name
49
+ image_list.add('image_file_1', 'key_name_1') # Specify image_1's key name
50
+ # Continue to add more images to the collection
51
+ ...
52
+
53
+ first_image = image_list['key_name_0'] # Get the first PhotoImage
54
+ second_image = image_list['key_name_1'] # Get the second PhotoImage
55
+
56
+ image_list.set_key_name(0, 'new_name') # Change the first PhotoImage's key name
57
+ ```
58
+
59
+ 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.
60
+
61
+ This example illustrates how the **ImageList** can be used to provide a "grayed" image to a Tkinter Button widget when it is made inactive:
62
+
63
+ ```
64
+ import tkinter as tk
65
+ import platform
66
+
67
+ image_list = ImageList()
68
+ image_list.add('image_file', 'key_name') # Add an image file and a key name
69
+
70
+ button = tk.Button(parent, image=image_list['key_name'], command=command)
71
+
72
+ # Make the button inactive and display a "grayed" image
73
+ button.configure(state='disabled')
74
+ if platform.system() == 'Darwin': # Check for the macOS platform
75
+ button.configure(image=image_list.grayed['key_name'])
76
+ ```
77
+
78
+ <div class="page"/>
79
+
80
+ # API Documentation
81
+
82
+ ## ImageList
83
+
84
+ ### ImageList( resource_folder=' ', image_size=( 16, 16 ), auto_load=False )
85
+
86
+ Constructs and initializes the PhotoImage collection.
87
+
88
+ * **resource_folder: str -** The path of the resource file folder for the image files, **default = ' '.**
89
+
90
+ * **image_size: tuple[ int, int ] -** The size ( width, height ) of the PhotoImages, **default = ( 16, 16 ) pixels.**
91
+
92
+ * **auto_load: bool -** Automatically load all the resource folder's image files if True, **default = False.**
93
+
94
+ 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.
95
+
96
+ ```
97
+ full_path_name = os.path.join(resource_folder, image_file)
98
+ ```
99
+
100
+ 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 ).
101
+
102
+ ```
103
+ extension = filename.rfind('.')
104
+ key_name = filename[:extension]
105
+ ```
106
+
107
+ ### Properties
108
+
109
+ * **blank_image: PhotoImage -** A blank ( or transparent ) PhotoImage. ( readonly )
110
+
111
+ * **grayed: ImageList.Grayed -** A managed collection of grayed PhotoImages. ( readonly )
112
+
113
+ * **image_size: tuple[ int, int ] -** The size ( width, height ) of the PhotoImages in the collection. ( readonly )
114
+
115
+ * **keys: list[ str ] -** A list of the key names currently assigned to the PhotoImages. ( readonly )
116
+
117
+ * **resource_folder: str -** The path of the resource file folder for the image files. ( read / write )
118
+
119
+ <div class="page"/>
120
+
121
+ ### Methods
122
+
123
+ * **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.
124
+ * **image_file: str -** The image file to add to the collection.
125
+ * **key_name: str | None -** The optional key name of the image (not case-sensitive).
126
+
127
+ **Returns : True** if the image was successfully added **, False** otherwise
128
+
129
+ * **clear( ) :** Remove all the images and key names from the collection.
130
+
131
+ * **contains_key( name ) -> bool :** Determine if the collection has an image with the specified key name.
132
+ * **name: str -** The specified key name of the image (not case-sensitive).
133
+
134
+ **Returns : True** if the collection contains the key name **, False** otherwise.
135
+
136
+ * **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.
137
+ * **image_list: ImageList -** The specified PhotoImage list.
138
+
139
+ **Returns : True** if the PhotoImage collection was successfully added **, False** otherwise.
140
+
141
+ * **index_of_key( name: str ) -> int :** Return the zero-based index of the image with the specified key name.
142
+ * **name: str -** The specified key name of the image (not case-sensitive).
143
+
144
+ **Returns :** The **index** of the first occurrence of the key name **, -1** otherwise.
145
+
146
+ * **remove_at( index: int ) :** Remove an image from the collection at the specified index.
147
+ * **index -** The zero-based index value of the image in the collection
148
+
149
+ * **remove_by_key( name: str ) :** Remove the image with the specified key name from the collection.
150
+ * **name: str -** The specified key name of the image (not case-sensitive).
151
+
152
+ * **set_key_name( index: int, name: str ) :** Set the key name for an image in the collection.
153
+ * **index -** The zero-based index value of the image in the collection.
154
+ * **name -** The name to be set as the image's key name (not case-sensitive).
155
+
156
+ <div class="page"/>
157
+
158
+ # ImageList Usage Examples
159
+
160
+ The source files and image files for these examples are available at the [imagelist GitHub Repository](https://github.com/johnbolk/imagelist).
161
+
162
+ 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.
163
+
164
+ ```
165
+ import platform
166
+ import tkinter as tk
167
+ from imagelist import ImageList
168
+
169
+
170
+ class MacButton(tk.Button):
171
+ """A macOS compatible image button widget."""
172
+
173
+ def __init__(self, parent, image_list, image_id, command):
174
+ """Create and initialize the macOS compatible image button widget."""
175
+ self._grayed = self._image = image_list[image_id]
176
+ if platform.system() == 'Darwin': # Check for the macOS platform
177
+ self._grayed = image_list.grayed[image_id]
178
+ width, height = image_list.image_size
179
+ super().__init__(parent, image=self._image, command=command)
180
+ self.config(width=width * 1.25, height=height * 1.25)
181
+
182
+ @property
183
+ def enabled(self):
184
+ """Get/Set the button enabled status."""
185
+ return self['state'] != tk.DISABLED
186
+
187
+ @enabled.setter
188
+ def enabled(self, enabled):
189
+ """Get/Set the button enabled status."""
190
+ self['state'] = tk.NORMAL if enabled else tk.DISABLED
191
+ self['image'] = self._image if enabled else self._grayed
192
+
193
+
194
+ class DemoForm(tk.Frame):
195
+ """The MacButton demo window."""
196
+
197
+ def __init__(self, master):
198
+ """Construct the MacButton demo window."""
199
+ super().__init__(master, bd=3, relief='ridge', padx=50, pady=20)
200
+ self.grid()
201
+
202
+ image_list = ImageList(image_size=(48, 48))
203
+ image_list.add('image_folder/printer.png') # Add an image of a printer
204
+ self._button = MacButton(self, image_list, 0, self._on_print)
205
+ self._button.enabled = False
206
+ self._button.grid(pady=20)
207
+
208
+ self._print_enable = tk.IntVar()
209
+ checkbox = tk.Checkbutton(self, text='Enable Print Button')
210
+ checkbox.config(variable=self._print_enable, command=self._on_check)
211
+ checkbox.grid()
212
+
213
+ def _on_check(self):
214
+ """Event handler for the Checkbutton."""
215
+ self._button.enabled = self._print_enable.get() != 0
216
+
217
+ @staticmethod
218
+ def _on_print():
219
+ """Event handler for the MacButton."""
220
+ print('*** Print ***')
221
+
222
+
223
+ if __name__ == '__main__':
224
+ main_form = DemoForm(tk.Tk())
225
+ main_form.mainloop()
226
+ ```
227
+
228
+ <div class="page"/>
229
+
230
+ 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.
231
+
232
+ ```
233
+ import tkinter as tk
234
+ from imagelist import ImageList
235
+
236
+
237
+ class DemoForm(tk.Frame):
238
+ """The image buttons demo window."""
239
+
240
+ def __init__(self, master):
241
+ """Construct the image buttons demo window."""
242
+ super().__init__(master, bd=3, relief='ridge', padx=20, pady=20)
243
+ self.grid()
244
+
245
+ group = tk.LabelFrame(self, text='Image Buttons')
246
+ image_list = ImageList('image_folder', (32, 32), True)
247
+ image_width, image_height = image_list.image_size
248
+ for i, name in enumerate(['printer', 'settings', 'help', 'stop']):
249
+
250
+ def handler(index=image_list.index_of_key(name)):
251
+ text = '???' if index < 0 else image_list.keys[index].title()
252
+ label.configure(text=f"'{text}' Button")
253
+
254
+ button = tk.Button(group, image=image_list[name], command=handler)
255
+ button.configure(width=image_width + 8, height=image_height + 8)
256
+ button.grid(column=i, row=0, padx=15, pady=(10, 15))
257
+ group.grid(pady=(0, 20))
258
+
259
+ label = tk.Label(self, bd=1, relief='solid', width=20, anchor='center')
260
+ label.configure(text='Click any Button')
261
+ label.grid()
262
+
263
+
264
+ if __name__ == '__main__':
265
+ main_form = DemoForm(tk.Tk())
266
+ main_form.mainloop()
267
+ ```
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=40.8.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "imagelist"
7
+ version = "1.2.0"
8
+ description = "A managed collection of PhotoImages for use with Tkinter widgets."
9
+ readme = "README.md"
10
+ authors = [
11
+ {name = "John Bolkcom", email = "johnbolk6502@gmail.com"}
12
+ ]
13
+ license = "MIT"
14
+ license-files = ["LICENSE"]
15
+ requires-python = ">=3.7"
16
+ dependencies = ["pillow>=9.5.0"]
17
+ keywords = ["Tkinter", "PhotoImage", "PIL", "macOS", "Icons"]
18
+ classifiers = [
19
+ "Programming Language :: Python :: 3",
20
+ "Operating System :: OS Independent",
21
+ "Intended Audience :: Developers",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/johnbolk/imagelist.git"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
@@ -0,0 +1,3 @@
1
+ __version__: str
2
+
3
+ from .imagelist import ImageList as ImageList
@@ -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)
@@ -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: ...
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,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/imagelist/__init__.py
5
+ src/imagelist/__init__.pyi
6
+ src/imagelist/imagelist.py
7
+ src/imagelist/imagelist.pyi
8
+ src/imagelist/py.typed
9
+ src/imagelist.egg-info/PKG-INFO
10
+ src/imagelist.egg-info/SOURCES.txt
11
+ src/imagelist.egg-info/dependency_links.txt
12
+ src/imagelist.egg-info/requires.txt
13
+ src/imagelist.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ pillow>=9.5.0
@@ -0,0 +1 @@
1
+ imagelist