treeviewex 1.0.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.
treeviewex/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .treeviewex import CellType, TreeviewEx
2
+
3
+ __all__ = ["CellType", "TreeviewEx"]
@@ -0,0 +1,599 @@
1
+ # python3
2
+ """Treeview extension."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from enum import Enum, auto
7
+ from tkinter import HORIZONTAL, VERTICAL, Entry, Event, Frame
8
+ from tkinter.ttk import Combobox, Scrollbar, Treeview
9
+ from typing import Callable, Union
10
+
11
+
12
+ class CellType(Enum):
13
+ """Enum defining cell types."""
14
+
15
+ ENTRY = auto()
16
+ READONLY = auto()
17
+ COMBOBOX = auto()
18
+
19
+
20
+ __all__ = ["CellType", "TreeviewEx"]
21
+
22
+
23
+ def _colid2colindex(column_id: str) -> int:
24
+ """
25
+ Convert a column ID to a column index.
26
+
27
+ Parameters
28
+ ----------
29
+ column_id : str
30
+ Column ID.
31
+
32
+ Returns
33
+ -------
34
+ int
35
+ Column index.
36
+
37
+ """
38
+ return int(column_id[1:]) - 1
39
+
40
+
41
+ class TreeviewEx(Treeview): # pylint: disable=too-many-ancestors
42
+ """Extended Treeview widget."""
43
+
44
+ def __init__(self, master=None, **kwargs):
45
+ """
46
+ Initialize the widget.
47
+
48
+ Parameters
49
+ ----------
50
+ master : widget, optional
51
+ Parent widget. The default is None.
52
+ **kwargs : dict
53
+ Additional options passed to tkinter.ttk.Treeview.
54
+
55
+ Returns
56
+ -------
57
+ None.
58
+
59
+ """
60
+ # Initialization
61
+ self.readonly_rows = set() # Keep read-only row IDs
62
+ self.readonly_columns = set() # Keep read-only column IDs
63
+ self.readonly_cells = set() # Keep read-only cells as (row, col)
64
+ self.combobox_rows = set() # Keep row IDs that use a combobox
65
+ self.combobox_columns = set() # Keep column IDs that use a combobox
66
+ self.combobox_cells = set() # Keep combobox cells as (row, col)
67
+ self.combobox_row_values = {} # Map row IDs to combobox value lists
68
+ self.combobox_column_values = {} # Map columns to combobox value lists
69
+ self.combobox_cell_values = {} # Map cells to combobox value lists
70
+
71
+ # Other initialization
72
+ self.frame = Frame(master=master)
73
+ super().__init__(self.frame, **kwargs)
74
+
75
+ # Create the Entry widget as a member
76
+ self.entry = Entry(self)
77
+ self.entry.bind("<Return>", self._on_return)
78
+ self.entry.bind("<FocusOut>", self._on_focus_out)
79
+ self.entry.bind("<Escape>", self._on_escape)
80
+
81
+ # Create the Combobox widget as a member
82
+ self.combobox = Combobox(self)
83
+ self.combobox.bind("<Return>", self._on_return)
84
+ self.combobox.bind("<Escape>", self._on_escape)
85
+ self.combobox.bind("<<ComboboxSelected>>", self._on_combobox_selected)
86
+
87
+ # Create a vertical scrollbar and connect it
88
+ self.scrollbar_y = Scrollbar(
89
+ self.frame, orient=VERTICAL, command=self._on_scroll_y
90
+ )
91
+ self.configure(yscrollcommand=self.scrollbar_y.set)
92
+
93
+ # Create a horizontal scrollbar and connect it
94
+ self.scrollbar_x = Scrollbar(
95
+ self.frame, orient=HORIZONTAL, command=self._on_scroll_x
96
+ )
97
+ self.configure(xscrollcommand=self.scrollbar_x.set)
98
+
99
+ super().grid(row=0, column=0, sticky="nsew")
100
+ self.scrollbar_y.grid(row=0, column=1, sticky="ns")
101
+ self.scrollbar_x.grid(row=1, column=0, sticky="ew")
102
+
103
+ # Set frame row/column weights to adjust the layout
104
+ self.frame.grid_rowconfigure(0, weight=1)
105
+ self.frame.grid_columnconfigure(0, weight=1)
106
+
107
+ # Bind additional behavior for the <Double-1> event
108
+ self._additional_bind_double_click()
109
+
110
+ # Bind the mouse wheel event
111
+ self.bind("<MouseWheel>", self._on_mouse_wheel)
112
+
113
+ # Variables to keep editing state
114
+ self._editing_cell = None
115
+ self._editing_combobox_values = None # Values for active combobox edit
116
+
117
+ def _on_scroll_y(self, *args):
118
+ """
119
+ Handle vertical scroll events.
120
+
121
+ Parameters
122
+ ----------
123
+ *args : tuple
124
+ Scrollbar callback arguments.
125
+
126
+ Returns
127
+ -------
128
+ None.
129
+
130
+ """
131
+ if self._editing_cell:
132
+ self.cancel_edit()
133
+ self.yview(*args)
134
+
135
+ def _on_scroll_x(self, *args):
136
+ """
137
+ Handle horizontal scroll events.
138
+
139
+ Parameters
140
+ ----------
141
+ *args : tuple
142
+ Scrollbar callback arguments.
143
+
144
+ Returns
145
+ -------
146
+ None.
147
+
148
+ """
149
+ if self._editing_cell:
150
+ self.cancel_edit()
151
+ self.xview(*args)
152
+
153
+ def _on_mouse_wheel(self, event):
154
+ """
155
+ Handle mouse wheel events.
156
+
157
+ Parameters
158
+ ----------
159
+ event : Event
160
+ Mouse wheel event.
161
+
162
+ Returns
163
+ -------
164
+ None.
165
+
166
+ """
167
+ if self._editing_cell:
168
+ self.cancel_edit()
169
+
170
+ # Run vertical scrolling
171
+ self.yview_scroll(-1 * (event.delta // 120), "units")
172
+
173
+ def _additional_bind_double_click(self):
174
+ """
175
+ Add a double-click handler.
176
+
177
+ Returns
178
+ -------
179
+ None.
180
+
181
+ """
182
+ # Keep the existing <Double-1> binding and add this handler
183
+ super().bind("<Double-1>", self._combined_handler, add="+")
184
+
185
+ def _combined_handler(self, event: Event):
186
+ """
187
+ Handle double-click events.
188
+
189
+ Parameters
190
+ ----------
191
+ event : Event
192
+ Event object.
193
+
194
+ Returns
195
+ -------
196
+ None.
197
+
198
+ """
199
+ self.on_double_click(event) # Additional behavior
200
+
201
+ def bind(
202
+ self,
203
+ sequence: str | None = None,
204
+ func: Callable | None = None,
205
+ add: bool | None = None,
206
+ ) -> str:
207
+ """
208
+ Override bind.
209
+
210
+ Parameters
211
+ ----------
212
+ sequence : str, optional
213
+ Same as the sequence argument of Treeview.bind().
214
+ func : Callable, optional
215
+ Same as the func argument of Treeview.bind().
216
+ add : bool, optional
217
+ Same as the add argument of Treeview.bind().
218
+
219
+ Returns
220
+ -------
221
+ str
222
+ Same return value as Treeview.bind().
223
+
224
+ """
225
+ if sequence == "<Double-1>":
226
+
227
+ def combined_handler(event):
228
+ self.on_double_click(event)
229
+ if func:
230
+ func(event)
231
+
232
+ return super().bind(sequence, combined_handler, add=add)
233
+ else:
234
+ return super().bind(sequence, func, add=add)
235
+
236
+ def pack(self, **kwargs):
237
+ """
238
+ Override pack.
239
+
240
+ Parameters
241
+ ----------
242
+ **kwargs : dict
243
+ Keyword arguments for pack.
244
+
245
+ Returns
246
+ -------
247
+ None.
248
+
249
+ """
250
+ self.frame.pack(**kwargs)
251
+
252
+ def grid(self, **kwargs):
253
+ """
254
+ Override grid.
255
+
256
+ Parameters
257
+ ----------
258
+ **kwargs : dict
259
+ Keyword arguments for grid.
260
+
261
+ Returns
262
+ -------
263
+ None.
264
+
265
+ """
266
+ self.frame.grid(**kwargs)
267
+
268
+ def column(self, column: str, option=None, **kw):
269
+ """
270
+ Override column.
271
+
272
+ Parameters
273
+ ----------
274
+ column : str
275
+ Column ID.
276
+ option : str, optional
277
+ Column option. The default is None.
278
+ **kw : dict
279
+ Additional keyword arguments.
280
+
281
+ Returns
282
+ -------
283
+ Any
284
+ Return value from Treeview.column().
285
+
286
+ """
287
+ if option is None and "stretch" not in kw:
288
+ kw["stretch"] = False
289
+ return super().column(column, option, **kw)
290
+
291
+ def get_clicked_cell_id_pair(self, event: Event) -> tuple:
292
+ """
293
+ Get the cell IDs at the clicked position.
294
+
295
+ Parameters
296
+ ----------
297
+ event : Event
298
+ Click event.
299
+
300
+ Returns
301
+ -------
302
+ tuple
303
+ Pair of (row ID, column ID).
304
+
305
+ """
306
+ cell_id_pair = ("", "")
307
+ region = self.identify_region(event.x, event.y)
308
+ if region != "cell":
309
+ return ("", "")
310
+ cell_id_pair = (
311
+ self.identify_row(event.y),
312
+ self.identify_column(event.x),
313
+ )
314
+ return cell_id_pair
315
+
316
+ def on_double_click(self, event: Event) -> None:
317
+ """
318
+ Handle double-click action.
319
+
320
+ Parameters
321
+ ----------
322
+ event : Event
323
+ Event object.
324
+
325
+ Returns
326
+ -------
327
+ None.
328
+
329
+ """
330
+ cell_id_pair = self.get_clicked_cell_id_pair(event)
331
+ if cell_id_pair != ("", ""):
332
+ self.start_edit(cell_id_pair)
333
+
334
+ def get_cell_value(self, cell_id_pair: tuple) -> str:
335
+ """
336
+ Get a cell value.
337
+
338
+ Parameters
339
+ ----------
340
+ cell_id_pair : tuple
341
+ Pair of (row ID, column ID).
342
+
343
+ Returns
344
+ -------
345
+ str
346
+ Cell value.
347
+
348
+ """
349
+ row_id, column_id = cell_id_pair
350
+ return self.item(row_id, "values")[_colid2colindex(column_id)]
351
+
352
+ def start_edit(self, cell_id_pair: tuple) -> None:
353
+ """Start editing a cell."""
354
+ if not self.is_valid_cell(cell_id_pair):
355
+ raise ValueError(f"Invalid cell specified: {cell_id_pair}")
356
+
357
+ row_id, column_id = cell_id_pair
358
+
359
+ cell_type = self._get_cell_type(cell_id_pair)
360
+
361
+ # Skip editing when the cell is read-only
362
+ if cell_type == CellType.READONLY:
363
+ return
364
+
365
+ # Continue with edit processing
366
+ self._editing_cell = cell_id_pair
367
+ cell_value = self.get_cell_value(cell_id_pair)
368
+
369
+ # Get the cell position and size
370
+ bbox = self.bbox(row_id, column_id)
371
+ if not bbox:
372
+ raise ValueError(
373
+ f"Cannot determine the position of the cell: {cell_id_pair}"
374
+ )
375
+
376
+ x, y, width, height = bbox
377
+
378
+ # For combobox cells
379
+ if cell_type == CellType.COMBOBOX:
380
+ # Keep the current value list
381
+ if cell_id_pair in self.combobox_cell_values:
382
+ self._editing_combobox_values = self.combobox_cell_values[
383
+ cell_id_pair
384
+ ]
385
+ elif row_id in self.combobox_row_values:
386
+ self._editing_combobox_values = self.combobox_row_values[row_id]
387
+ elif column_id in self.combobox_column_values:
388
+ self._editing_combobox_values = self.combobox_column_values[
389
+ column_id
390
+ ]
391
+ else:
392
+ self._editing_combobox_values = []
393
+
394
+ # Configure the Combobox widget
395
+ self.combobox.delete(0, "end")
396
+ self.combobox.insert(0, cell_value)
397
+ self.combobox["values"] = self._editing_combobox_values
398
+
399
+ self.combobox.place(x=x, y=y, width=width, height=height)
400
+ self.combobox.focus_set()
401
+ elif cell_type == CellType.ENTRY:
402
+ # Configure the Entry widget
403
+ self.entry.delete(0, "end")
404
+ self.entry.insert(0, cell_value)
405
+ self.entry.place(x=x, y=y, width=width, height=height)
406
+ self.entry.focus_set()
407
+
408
+ def is_valid_cell(self, cell_id_pair: tuple) -> bool:
409
+ """
410
+ Check whether a cell exists.
411
+
412
+ Parameters
413
+ ----------
414
+ cell_id_pair : tuple
415
+ Pair of (row ID, column ID).
416
+
417
+ Returns
418
+ -------
419
+ bool
420
+ True if the cell is valid, otherwise False.
421
+
422
+ """
423
+ row_id, column_id = cell_id_pair
424
+ try:
425
+ col_index = _colid2colindex(column_id) # Convert column ID to index
426
+ except (ValueError, IndexError): # pragma: no cover
427
+ return False # pragma: no cover
428
+
429
+ if row_id not in self.get_children() or col_index >= len(
430
+ self["columns"]
431
+ ):
432
+ return False
433
+
434
+ return True
435
+
436
+ def _on_return(self, event): # pylint: disable=unused-argument
437
+ """Handle the <Return> event."""
438
+ if self._editing_cell:
439
+ widget = event.widget
440
+ self.update_cell(self._editing_cell, widget)
441
+
442
+ def _on_focus_out(self, event): # pylint: disable=unused-argument
443
+ """Handle the <FocusOut> event."""
444
+ if self._editing_cell:
445
+ widget = event.widget
446
+ self.update_cell(self._editing_cell, widget)
447
+
448
+ def _on_escape(self, event): # pylint: disable=unused-argument
449
+ """Handle the <Escape> event."""
450
+ self.cancel_edit()
451
+
452
+ def _on_combobox_selected(self, event): # pylint: disable=unused-argument
453
+ """Handle combobox selection events."""
454
+ if self._editing_cell:
455
+ widget = event.widget
456
+ self.update_cell(self._editing_cell, widget)
457
+
458
+ def _get_cell_type(self, cell_id_pair: tuple) -> CellType:
459
+ """
460
+ Determine a cell type.
461
+
462
+ Parameters
463
+ ----------
464
+ cell_id_pair : tuple
465
+ Pair of (row ID, column ID).
466
+
467
+ Returns
468
+ -------
469
+ CellType
470
+ CellType.READONLY, CellType.COMBOBOX, or CellType.ENTRY.
471
+
472
+ """
473
+ row_id, column_id = cell_id_pair
474
+
475
+ # Check read-only settings
476
+ if (
477
+ row_id in self.readonly_rows
478
+ or column_id in self.readonly_columns
479
+ or cell_id_pair in self.readonly_cells
480
+ ):
481
+ return CellType.READONLY
482
+
483
+ # Check combobox settings
484
+ if (
485
+ row_id in self.combobox_rows
486
+ or column_id in self.combobox_columns
487
+ or cell_id_pair in self.combobox_cells
488
+ ):
489
+ return CellType.COMBOBOX
490
+
491
+ return CellType.ENTRY
492
+
493
+ def update_cell(
494
+ self, cell_id_pair: tuple, widget: Union[Entry, Combobox]
495
+ ) -> None:
496
+ """Update a cell value."""
497
+ if not self.is_valid_cell(cell_id_pair):
498
+ raise ValueError(f"Invalid cell specified: {cell_id_pair}")
499
+
500
+ cell_type = self._get_cell_type(cell_id_pair)
501
+
502
+ # Do not update when the cell is read-only
503
+ if cell_type == CellType.READONLY:
504
+ self.cancel_edit()
505
+ return
506
+
507
+ # Update value for ENTRY or COMBOBOX cells
508
+ if cell_type == CellType.ENTRY or cell_type == CellType.COMBOBOX:
509
+ # Get the new value
510
+ new_value = widget.get()
511
+ # Update only when the value changed
512
+ if new_value != self.get_cell_value(cell_id_pair):
513
+ values = list(self.item(cell_id_pair[0], "values"))
514
+ col_index = _colid2colindex(cell_id_pair[1])
515
+ values[col_index] = new_value
516
+ self.item(cell_id_pair[0], values=values)
517
+
518
+ self.cancel_edit()
519
+
520
+ def cancel_edit(self):
521
+ """
522
+ Cancel editing.
523
+
524
+ Returns
525
+ -------
526
+ None.
527
+
528
+ """
529
+ self.entry.place_forget() # Hide Entry
530
+ self.combobox.place_forget() # Hide Combobox
531
+ self._editing_cell = None
532
+ self._editing_combobox_values = None
533
+
534
+ def set_readonly_row(self, row_id: str, readonly: bool = True) -> None:
535
+ """Set a row as read-only."""
536
+ if readonly:
537
+ self.readonly_rows.add(row_id)
538
+ else:
539
+ self.readonly_rows.discard(row_id)
540
+
541
+ def set_readonly_column(
542
+ self, column_id: str, readonly: bool = True
543
+ ) -> None:
544
+ """Set a column as read-only."""
545
+ if readonly:
546
+ self.readonly_columns.add(column_id)
547
+ else:
548
+ self.readonly_columns.discard(column_id)
549
+
550
+ def set_readonly_cell(
551
+ self, cell_id_pair: tuple, readonly: bool = True
552
+ ) -> None:
553
+ """Set a cell as read-only."""
554
+ if readonly:
555
+ self.readonly_cells.add(cell_id_pair)
556
+ else:
557
+ self.readonly_cells.discard(cell_id_pair)
558
+
559
+ def set_combobox_row(
560
+ self, row_id: str, values: list | None = None, is_combobox: bool = True
561
+ ) -> None:
562
+ """Set a row to use a combobox."""
563
+ if is_combobox:
564
+ self.combobox_rows.add(row_id)
565
+ if values is not None:
566
+ self.combobox_row_values[row_id] = values
567
+ else:
568
+ self.combobox_rows.discard(row_id)
569
+ self.combobox_row_values.pop(row_id, None)
570
+
571
+ def set_combobox_column(
572
+ self,
573
+ column_id: str,
574
+ values: list | None = None,
575
+ is_combobox: bool = True,
576
+ ) -> None:
577
+ """Set a column to use a combobox."""
578
+ if is_combobox:
579
+ self.combobox_columns.add(column_id)
580
+ if values is not None:
581
+ self.combobox_column_values[column_id] = values
582
+ else:
583
+ self.combobox_columns.discard(column_id)
584
+ self.combobox_column_values.pop(column_id, None)
585
+
586
+ def set_combobox_cell(
587
+ self,
588
+ cell_id_pair: tuple,
589
+ values: list | None = None,
590
+ is_combobox: bool = True,
591
+ ) -> None:
592
+ """Set a cell to use a combobox."""
593
+ if is_combobox:
594
+ self.combobox_cells.add(cell_id_pair)
595
+ if values is not None:
596
+ self.combobox_cell_values[cell_id_pair] = values
597
+ else:
598
+ self.combobox_cells.discard(cell_id_pair)
599
+ self.combobox_cell_values.pop(cell_id_pair, None)
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: treeviewex
3
+ Version: 1.0.0
4
+ Summary: A TreeView extension for Tkinter
5
+ Project-URL: Homepage, https://github.com/fangface-hub/treeviewex
6
+ Project-URL: Repository, https://github.com/fangface-hub/treeviewex
7
+ Project-URL: Issues, https://github.com/fangface-hub/treeviewex/issues
8
+ Author: fangface
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: tkinter,treeview,ui
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+
15
+ # TreeviewEx
16
+
17
+ [日本語](README_ja.md)
18
+
19
+ Treeview extension for Tkinter.
20
+
21
+ This package adds the following features to the standard tkinter Treeview:
22
+
23
+ - Vertical and horizontal scroll bars
24
+ - Cell editing
25
+ - Read-only settings for rows, columns, and cells
26
+
27
+ All other behavior is the same as the standard tkinter Treeview.
28
+
29
+ ---
30
+
31
+ ## Testing
32
+
33
+ 1. Install dependencies
34
+
35
+ ```bash
36
+ uv sync --group dev
37
+ ```
38
+
39
+ 2. Run the tests
40
+
41
+ ```bash
42
+ uv run pytest -q
43
+ ```
44
+
45
+ 3. Show coverage report
46
+
47
+ ```bash
48
+ uv run coverage run -m pytest -q
49
+ uv run coverage report -m
50
+ ```
51
+
52
+ 4. Generate an HTML report (optional)
53
+
54
+ ```bash
55
+ uv run coverage html
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Build
61
+
62
+ 1. Install dependencies
63
+
64
+ ```bash
65
+ uv sync --group dev
66
+ ```
67
+
68
+ 2. Build the package
69
+
70
+ ```bash
71
+ uv build
72
+ ```
73
+
74
+ 3. Check the generated wheel in the dist folder
75
+
76
+ ---
77
+
78
+ ## Usage
79
+
80
+ 1. Install the package
81
+
82
+ ```bash
83
+ uv pip install path/to/TreeviewEx.whl
84
+ ```
85
+
86
+ 2. Run the sample
87
+
88
+ ```bash
89
+ uv run python sample/sample.py
90
+ ```
91
+
92
+ Example:
93
+
94
+ ```python
95
+ from treeviewex import TreeviewEx
96
+ from tkinter import Tk
97
+
98
+ root = Tk()
99
+ root.title("TreeviewEx Example")
100
+
101
+ treeview_ex = TreeviewEx(root)
102
+ treeview_ex.grid(row=0, column=0, sticky="nsew")
103
+
104
+ columns = ("col1", "col2", "col3", "col4")
105
+ treeview_ex["columns"] = columns
106
+ treeview_ex.heading("#0", text="", anchor="w")
107
+ treeview_ex.column("#0", width=0, stretch=False)
108
+ for col in columns:
109
+ treeview_ex.heading(col, text=f"{col.capitalize()}")
110
+ treeview_ex.column(col, width=100)
111
+
112
+ for i in range(100):
113
+ treeview_ex.insert(
114
+ "",
115
+ "end",
116
+ text="",
117
+ values=(f"Value {i}A", f"Value {i}B", f"Value {i}C", f"Value {i}D"),
118
+ )
119
+
120
+ root.grid_rowconfigure(0, weight=1)
121
+ root.grid_columnconfigure(0, weight=1)
122
+
123
+ root.mainloop()
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Public interfaces
129
+
130
+ ### set_readonly_row(row_id: str, readonly: bool = True) -> None
131
+
132
+ Set the specified row to readonly.
133
+
134
+ ### set_readonly_column(column_id: str, readonly: bool = True) -> None
135
+
136
+ Set the specified column to readonly.
137
+
138
+ ### set_readonly_cell(cell_id_pair: tuple, readonly: bool = True) -> None
139
+
140
+ Set the specified cell to readonly.
141
+
142
+ ### set_combobox_row(row_id: str, values: list = None, is_combobox: bool = True) -> None
143
+
144
+ Set the specified row to be editable with a Combobox.
145
+
146
+ ### set_combobox_column(column_id: str, values: list = None, is_combobox: bool = True) -> None
147
+
148
+ Set the specified column to be editable with a Combobox.
149
+
150
+ ### set_combobox_cell(cell_id_pair: tuple, values: list = None, is_combobox: bool = True) -> None
151
+
152
+ Set the specified cell to be editable with a Combobox.
153
+
154
+ ---
155
+
156
+ ## License
157
+
158
+ This project is licensed under the MIT License.
@@ -0,0 +1,6 @@
1
+ treeviewex/__init__.py,sha256=nBsuFVnD6AqRyBqmp-d8a6MG-lVR7IZLEGOOetCgXfg,86
2
+ treeviewex/treeviewex.py,sha256=XrYvhH9eNACXt8JAyykC8nT9EnQCOaH9PsuuuvmNg-s,16838
3
+ treeviewex-1.0.0.dist-info/METADATA,sha256=wf1_xn9tYHI8piyJ1l9YeP6mcEb-5ZA4YEMIANyUQbc,3089
4
+ treeviewex-1.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
5
+ treeviewex-1.0.0.dist-info/licenses/LICENSE,sha256=CIAHTomy63TLfdjZOibgqUQd6-IEmm7raWIxEqizNo8,1065
6
+ treeviewex-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 fangface
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.