urwid 2.6.0.post0__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.

Potentially problematic release.


This version of urwid might be problematic. Click here for more details.

Files changed (75) hide show
  1. urwid/__init__.py +333 -0
  2. urwid/canvas.py +1413 -0
  3. urwid/command_map.py +137 -0
  4. urwid/container.py +59 -0
  5. urwid/decoration.py +65 -0
  6. urwid/display/__init__.py +97 -0
  7. urwid/display/_posix_raw_display.py +413 -0
  8. urwid/display/_raw_display_base.py +914 -0
  9. urwid/display/_web.css +12 -0
  10. urwid/display/_web.js +462 -0
  11. urwid/display/_win32.py +171 -0
  12. urwid/display/_win32_raw_display.py +269 -0
  13. urwid/display/common.py +1219 -0
  14. urwid/display/curses.py +690 -0
  15. urwid/display/escape.py +624 -0
  16. urwid/display/html_fragment.py +251 -0
  17. urwid/display/lcd.py +518 -0
  18. urwid/display/raw.py +37 -0
  19. urwid/display/web.py +636 -0
  20. urwid/event_loop/__init__.py +55 -0
  21. urwid/event_loop/abstract_loop.py +175 -0
  22. urwid/event_loop/asyncio_loop.py +231 -0
  23. urwid/event_loop/glib_loop.py +294 -0
  24. urwid/event_loop/main_loop.py +721 -0
  25. urwid/event_loop/select_loop.py +230 -0
  26. urwid/event_loop/tornado_loop.py +206 -0
  27. urwid/event_loop/trio_loop.py +302 -0
  28. urwid/event_loop/twisted_loop.py +269 -0
  29. urwid/event_loop/zmq_loop.py +275 -0
  30. urwid/font.py +695 -0
  31. urwid/graphics.py +96 -0
  32. urwid/highlight.css +19 -0
  33. urwid/listbox.py +1899 -0
  34. urwid/monitored_list.py +522 -0
  35. urwid/numedit.py +376 -0
  36. urwid/signals.py +330 -0
  37. urwid/split_repr.py +130 -0
  38. urwid/str_util.py +358 -0
  39. urwid/text_layout.py +632 -0
  40. urwid/treetools.py +515 -0
  41. urwid/util.py +557 -0
  42. urwid/version.py +16 -0
  43. urwid/vterm.py +1806 -0
  44. urwid/widget/__init__.py +181 -0
  45. urwid/widget/attr_map.py +161 -0
  46. urwid/widget/attr_wrap.py +140 -0
  47. urwid/widget/bar_graph.py +649 -0
  48. urwid/widget/big_text.py +77 -0
  49. urwid/widget/box_adapter.py +126 -0
  50. urwid/widget/columns.py +1145 -0
  51. urwid/widget/constants.py +574 -0
  52. urwid/widget/container.py +227 -0
  53. urwid/widget/divider.py +110 -0
  54. urwid/widget/edit.py +718 -0
  55. urwid/widget/filler.py +403 -0
  56. urwid/widget/frame.py +539 -0
  57. urwid/widget/grid_flow.py +539 -0
  58. urwid/widget/line_box.py +194 -0
  59. urwid/widget/overlay.py +829 -0
  60. urwid/widget/padding.py +597 -0
  61. urwid/widget/pile.py +971 -0
  62. urwid/widget/popup.py +170 -0
  63. urwid/widget/progress_bar.py +141 -0
  64. urwid/widget/scrollable.py +597 -0
  65. urwid/widget/solid_fill.py +44 -0
  66. urwid/widget/text.py +354 -0
  67. urwid/widget/widget.py +852 -0
  68. urwid/widget/widget_decoration.py +166 -0
  69. urwid/widget/wimp.py +792 -0
  70. urwid/wimp.py +23 -0
  71. urwid-2.6.0.post0.dist-info/COPYING +504 -0
  72. urwid-2.6.0.post0.dist-info/METADATA +332 -0
  73. urwid-2.6.0.post0.dist-info/RECORD +75 -0
  74. urwid-2.6.0.post0.dist-info/WHEEL +5 -0
  75. urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,539 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ import warnings
5
+
6
+ from urwid.monitored_list import MonitoredFocusList, MonitoredList
7
+
8
+ from .columns import Columns
9
+ from .constants import Align, Sizing, WHSettings
10
+ from .container import WidgetContainerListContentsMixin, WidgetContainerMixin
11
+ from .divider import Divider
12
+ from .padding import Padding
13
+ from .pile import Pile
14
+ from .widget import Widget, WidgetError, WidgetWrap
15
+
16
+ if typing.TYPE_CHECKING:
17
+ from collections.abc import Iterable, Sequence
18
+
19
+ from typing_extensions import Literal
20
+
21
+
22
+ class GridFlowError(WidgetError):
23
+ pass
24
+
25
+
26
+ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListContentsMixin):
27
+ """
28
+ The GridFlow widget is a flow widget that renders all the widgets it
29
+ contains the same width and it arranges them from left to right and top to
30
+ bottom.
31
+ """
32
+
33
+ def sizing(self) -> frozenset[Sizing]:
34
+ return frozenset((Sizing.FLOW, Sizing.FIXED))
35
+
36
+ def __init__(
37
+ self,
38
+ cells: Iterable[Widget],
39
+ cell_width: int,
40
+ h_sep: int,
41
+ v_sep: int,
42
+ align: Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int],
43
+ focus: int | Widget | None = None,
44
+ ) -> None:
45
+ """
46
+ :param cells: iterable of flow widgets to display
47
+ :param cell_width: column width for each cell
48
+ :param h_sep: blank columns between each cell horizontally
49
+ :param v_sep: blank rows between cells vertically
50
+ (if more than one row is required to display all the cells)
51
+ :param align: horizontal alignment of cells, one of:
52
+ 'left', 'center', 'right', ('relative', percentage 0=left 100=right)
53
+ :param focus: widget index or widget instance to focus on
54
+ """
55
+ prepared_contents: list[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]] = []
56
+ focus_position: int = -1
57
+
58
+ for idx, widget in enumerate(cells):
59
+ prepared_contents.append((widget, (WHSettings.GIVEN, cell_width)))
60
+ if focus_position < 0 and (focus in {widget, idx} or (focus is None and widget.selectable())):
61
+ focus_position = idx
62
+
63
+ focus_position = max(focus_position, 0)
64
+
65
+ self._contents: MonitoredFocusList[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]] = MonitoredFocusList(
66
+ prepared_contents, focus=focus_position
67
+ )
68
+ self._contents.set_modified_callback(self._invalidate)
69
+ self._contents.set_focus_changed_callback(lambda f: self._invalidate())
70
+ self._contents.set_validate_contents_modified(self._contents_modified)
71
+ self._cell_width = cell_width
72
+ self.h_sep = h_sep
73
+ self.v_sep = v_sep
74
+ self.align = align
75
+ self._cache_maxcol = self._get_maxcol(())
76
+ super().__init__(self.generate_display_widget((self._cache_maxcol,)))
77
+
78
+ def _invalidate(self) -> None:
79
+ self._cache_maxcol = None
80
+ super()._invalidate()
81
+
82
+ def _contents_modified(
83
+ self, slc, new_items: Iterable[tuple[Widget, tuple[Literal["given", WHSettings.GIVEN], int]]]
84
+ ) -> None:
85
+ for item in new_items:
86
+ try:
87
+ _w, (t, _n) = item
88
+ if t != WHSettings.GIVEN:
89
+ raise GridFlowError(f"added content invalid {item!r}")
90
+ except (TypeError, ValueError) as exc: # noqa: PERF203
91
+ raise GridFlowError(f"added content invalid {item!r}").with_traceback(exc.__traceback__) from exc
92
+
93
+ @property
94
+ def cells(self):
95
+ """
96
+ A list of the widgets in this GridFlow
97
+
98
+ .. note:: only for backwards compatibility. You should use the new
99
+ standard container property :attr:`contents` to modify GridFlow
100
+ contents.
101
+ """
102
+ warnings.warn(
103
+ "only for backwards compatibility."
104
+ "You should use the new standard container property `contents` to modify GridFlow",
105
+ PendingDeprecationWarning,
106
+ stacklevel=2,
107
+ )
108
+ ml = MonitoredList(w for w, t in self.contents)
109
+
110
+ def user_modified():
111
+ self.cells = ml
112
+
113
+ ml.set_modified_callback(user_modified)
114
+ return ml
115
+
116
+ @cells.setter
117
+ def cells(self, widgets: Sequence[Widget]):
118
+ warnings.warn(
119
+ "only for backwards compatibility."
120
+ "You should use the new standard container property `contents` to modify GridFlow",
121
+ PendingDeprecationWarning,
122
+ stacklevel=2,
123
+ )
124
+ focus_position = self.focus_position
125
+ self.contents = [(new, (WHSettings.GIVEN, self._cell_width)) for new in widgets]
126
+ if focus_position < len(widgets):
127
+ self.focus_position = focus_position
128
+
129
+ def _get_cells(self):
130
+ warnings.warn(
131
+ "only for backwards compatibility."
132
+ "You should use the new standard container property `contents` to modify GridFlow",
133
+ DeprecationWarning,
134
+ stacklevel=3,
135
+ )
136
+ return self.cells
137
+
138
+ def _set_cells(self, widgets: Sequence[Widget]):
139
+ warnings.warn(
140
+ "only for backwards compatibility."
141
+ "You should use the new standard container property `contents` to modify GridFlow",
142
+ DeprecationWarning,
143
+ stacklevel=3,
144
+ )
145
+ self.cells = widgets
146
+
147
+ @property
148
+ def cell_width(self) -> int:
149
+ """
150
+ The width of each cell in the GridFlow. Setting this value affects
151
+ all cells.
152
+ """
153
+ return self._cell_width
154
+
155
+ @cell_width.setter
156
+ def cell_width(self, width: int) -> None:
157
+ focus_position = self.focus_position
158
+ self.contents = [(w, (WHSettings.GIVEN, width)) for (w, options) in self.contents]
159
+ self.focus_position = focus_position
160
+ self._cell_width = width
161
+
162
+ def _get_cell_width(self) -> int:
163
+ warnings.warn(
164
+ f"Method `{self.__class__.__name__}._get_cell_width` is deprecated, "
165
+ f"please use property `{self.__class__.__name__}.cell_width`",
166
+ DeprecationWarning,
167
+ stacklevel=3,
168
+ )
169
+ return self.cell_width
170
+
171
+ def _set_cell_width(self, width: int) -> None:
172
+ warnings.warn(
173
+ f"Method `{self.__class__.__name__}._set_cell_width` is deprecated, "
174
+ f"please use property `{self.__class__.__name__}.cell_width`",
175
+ DeprecationWarning,
176
+ stacklevel=3,
177
+ )
178
+ self.cell_width = width
179
+
180
+ @property
181
+ def contents(self) -> MonitoredFocusList[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]]:
182
+ """
183
+ The contents of this GridFlow as a list of (widget, options)
184
+ tuples.
185
+
186
+ options is currently a tuple in the form `('fixed', number)`.
187
+ number is the number of screen columns to allocate to this cell.
188
+ 'fixed' is the only type accepted at this time.
189
+
190
+ This list may be modified like a normal list and the GridFlow
191
+ widget will update automatically.
192
+
193
+ .. seealso:: Create new options tuples with the :meth:`options` method.
194
+ """
195
+ return self._contents
196
+
197
+ @contents.setter
198
+ def contents(self, c):
199
+ self._contents[:] = c
200
+
201
+ def options(
202
+ self,
203
+ width_type: Literal["given", WHSettings.GIVEN] = WHSettings.GIVEN,
204
+ width_amount: int | None = None,
205
+ ) -> tuple[Literal[WHSettings.GIVEN], int]:
206
+ """
207
+ Return a new options tuple for use in a GridFlow's .contents list.
208
+
209
+ width_type -- 'given' is the only value accepted
210
+ width_amount -- None to use the default cell_width for this GridFlow
211
+ """
212
+ if width_type != WHSettings.GIVEN:
213
+ raise GridFlowError(f"invalid width_type: {width_type!r}")
214
+ if width_amount is None:
215
+ width_amount = self._cell_width
216
+ return (WHSettings(width_type), width_amount)
217
+
218
+ def set_focus(self, cell: Widget | int) -> None:
219
+ """
220
+ Set the cell in focus, for backwards compatibility.
221
+
222
+ .. note:: only for backwards compatibility. You may also use the new
223
+ standard container property :attr:`focus_position` to get the focus.
224
+
225
+ :param cell: contained element to focus
226
+ :type cell: Widget or int
227
+ """
228
+ warnings.warn(
229
+ "only for backwards compatibility."
230
+ "You may also use the new standard container property `focus_position` to set the focus.",
231
+ PendingDeprecationWarning,
232
+ stacklevel=2,
233
+ )
234
+ if isinstance(cell, int):
235
+ try:
236
+ if cell < 0 or cell >= len(self.contents):
237
+ raise IndexError(f"No GridFlow child widget at position {cell}")
238
+ except TypeError as exc:
239
+ raise IndexError(f"No GridFlow child widget at position {cell}").with_traceback(
240
+ exc.__traceback__
241
+ ) from exc
242
+ self.contents.focus = cell
243
+ return
244
+
245
+ for i, (w, _options) in enumerate(self.contents):
246
+ if cell == w:
247
+ self.focus_position = i
248
+ return
249
+ raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")
250
+
251
+ @property
252
+ def focus(self) -> Widget | None:
253
+ """the child widget in focus or None when GridFlow is empty"""
254
+ if not self.contents:
255
+ return None
256
+ return self.contents[self.focus_position][0]
257
+
258
+ def _get_focus(self) -> Widget:
259
+ warnings.warn(
260
+ f"method `{self.__class__.__name__}._get_focus` is deprecated, "
261
+ f"please use `{self.__class__.__name__}.focus` property",
262
+ DeprecationWarning,
263
+ stacklevel=3,
264
+ )
265
+ if not self.contents:
266
+ return None
267
+ return self.contents[self.focus_position][0]
268
+
269
+ def get_focus(self):
270
+ """
271
+ Return the widget in focus, for backwards compatibility.
272
+
273
+ .. note:: only for backwards compatibility. You may also use the new
274
+ standard container property :attr:`focus` to get the focus.
275
+ """
276
+ warnings.warn(
277
+ "only for backwards compatibility."
278
+ "You may also use the new standard container property `focus` to get the focus.",
279
+ PendingDeprecationWarning,
280
+ stacklevel=2,
281
+ )
282
+ if not self.contents:
283
+ return None
284
+ return self.contents[self.focus_position][0]
285
+
286
+ @property
287
+ def focus_cell(self):
288
+ warnings.warn(
289
+ "only for backwards compatibility."
290
+ "You may also use the new standard container property"
291
+ "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
292
+ PendingDeprecationWarning,
293
+ stacklevel=2,
294
+ )
295
+ return self.focus
296
+
297
+ @focus_cell.setter
298
+ def focus_cell(self, cell: Widget) -> None:
299
+ warnings.warn(
300
+ "only for backwards compatibility."
301
+ "You may also use the new standard container property"
302
+ "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
303
+ PendingDeprecationWarning,
304
+ stacklevel=2,
305
+ )
306
+ for i, (w, _options) in enumerate(self.contents):
307
+ if cell == w:
308
+ self.focus_position = i
309
+ return
310
+ raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")
311
+
312
+ def _set_focus_cell(self, cell: Widget) -> None:
313
+ warnings.warn(
314
+ "only for backwards compatibility."
315
+ "You may also use the new standard container property"
316
+ "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
317
+ DeprecationWarning,
318
+ stacklevel=3,
319
+ )
320
+ for i, (w, _options) in enumerate(self.contents):
321
+ if cell == w:
322
+ self.focus_position = i
323
+ return
324
+ raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")
325
+
326
+ @property
327
+ def focus_position(self) -> int | None:
328
+ """
329
+ index of child widget in focus.
330
+ Raises :exc:`IndexError` if read when GridFlow is empty, or when set to an invalid index.
331
+ """
332
+ if not self.contents:
333
+ raise IndexError("No focus_position, GridFlow is empty")
334
+ return self.contents.focus
335
+
336
+ @focus_position.setter
337
+ def focus_position(self, position: int) -> None:
338
+ """
339
+ Set the widget in focus.
340
+
341
+ position -- index of child widget to be made focus
342
+ """
343
+ try:
344
+ if position < 0 or position >= len(self.contents):
345
+ raise IndexError(f"No GridFlow child widget at position {position}")
346
+ except TypeError as exc:
347
+ raise IndexError(f"No GridFlow child widget at position {position}").with_traceback(
348
+ exc.__traceback__
349
+ ) from exc
350
+ self.contents.focus = position
351
+
352
+ def _get_focus_position(self) -> int | None:
353
+ warnings.warn(
354
+ f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
355
+ f"please use `{self.__class__.__name__}.focus_position` property",
356
+ DeprecationWarning,
357
+ stacklevel=3,
358
+ )
359
+ if not self.contents:
360
+ raise IndexError("No focus_position, GridFlow is empty")
361
+ return self.contents.focus
362
+
363
+ def _set_focus_position(self, position: int) -> None:
364
+ """
365
+ Set the widget in focus.
366
+
367
+ position -- index of child widget to be made focus
368
+ """
369
+ warnings.warn(
370
+ f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
371
+ f"please use `{self.__class__.__name__}.focus_position` property",
372
+ DeprecationWarning,
373
+ stacklevel=3,
374
+ )
375
+ try:
376
+ if position < 0 or position >= len(self.contents):
377
+ raise IndexError(f"No GridFlow child widget at position {position}")
378
+ except TypeError as exc:
379
+ raise IndexError(f"No GridFlow child widget at position {position}").with_traceback(
380
+ exc.__traceback__
381
+ ) from exc
382
+ self.contents.focus = position
383
+
384
+ def _get_maxcol(self, size: tuple[int] | tuple[()]) -> int:
385
+ if size:
386
+ (maxcol,) = size
387
+ else:
388
+ maxcol = len(self) * self.cell_width + (len(self) - 1) * self.h_sep
389
+ return maxcol
390
+
391
+ def get_display_widget(self, size: tuple[int] | tuple[()]) -> Divider | Pile:
392
+ """
393
+ Arrange the cells into columns (and possibly a pile) for
394
+ display, input or to calculate rows, and update the display
395
+ widget.
396
+ """
397
+ maxcol = self._get_maxcol(size)
398
+
399
+ # use cache if possible
400
+ if self._cache_maxcol == maxcol:
401
+ return self._w
402
+
403
+ self._cache_maxcol = maxcol
404
+ self._w = self.generate_display_widget((maxcol,))
405
+
406
+ return self._w
407
+
408
+ def generate_display_widget(self, size: tuple[int] | tuple[()]) -> Divider | Pile:
409
+ """
410
+ Actually generate display widget (ignoring cache)
411
+ """
412
+ maxcol = self._get_maxcol(size)
413
+
414
+ divider = Divider()
415
+ if not self.contents:
416
+ return divider
417
+
418
+ if self.v_sep > 1:
419
+ # increase size of divider
420
+ divider.top = self.v_sep - 1
421
+
422
+ c = None
423
+ p = Pile([])
424
+ used_space = 0
425
+
426
+ for i, (w, (_width_type, width_amount)) in enumerate(self.contents):
427
+ if c is None or maxcol - used_space < width_amount:
428
+ # starting a new row
429
+ if self.v_sep:
430
+ p.contents.append((divider, p.options()))
431
+ c = Columns([], self.h_sep)
432
+ column_focused = False
433
+ pad = Padding(c, self.align)
434
+ # extra attribute to reference contents position
435
+ pad.first_position = i
436
+ p.contents.append((pad, p.options()))
437
+
438
+ c.contents.append((w, c.options(WHSettings.GIVEN, width_amount)))
439
+ if (i == self.focus_position) or (not column_focused and w.selectable()):
440
+ c.focus_position = len(c.contents) - 1
441
+ column_focused = True
442
+ if i == self.focus_position:
443
+ p.focus_position = len(p.contents) - 1
444
+ used_space = sum(x[1][1] for x in c.contents) + self.h_sep * len(c.contents)
445
+ if width_amount > maxcol:
446
+ # special case: display is too narrow for the given
447
+ # width so we remove the Columns for better behaviour
448
+ # FIXME: determine why this is necessary
449
+ pad.original_widget = w
450
+ pad.width = used_space - self.h_sep
451
+
452
+ if self.v_sep:
453
+ # remove first divider
454
+ del p.contents[:1]
455
+ else:
456
+ # Ensure p __selectable is updated
457
+ p._contents_modified() # pylint: disable=protected-access
458
+
459
+ return p
460
+
461
+ def _set_focus_from_display_widget(self) -> None:
462
+ """
463
+ Set the focus to the item in focus in the display widget.
464
+ """
465
+ # display widget (self._w) is always built as:
466
+ #
467
+ # Pile([
468
+ # Padding(
469
+ # Columns([ # possibly
470
+ # cell, ...])),
471
+ # Divider(), # possibly
472
+ # ...])
473
+
474
+ pile_focus = self._w.focus
475
+ if not pile_focus:
476
+ return
477
+ c = pile_focus.base_widget
478
+ if c.focus:
479
+ col_focus_position = c.focus_position
480
+ else:
481
+ col_focus_position = 0
482
+ # pad.first_position was set by generate_display_widget() above
483
+ self.focus_position = pile_focus.first_position + col_focus_position
484
+
485
+ def keypress(self, size: tuple[int], key: str) -> str | None:
486
+ """
487
+ Pass keypress to display widget for handling.
488
+ Captures focus changes.
489
+ """
490
+ self.get_display_widget(size)
491
+ key = super().keypress(size, key)
492
+ if key is None:
493
+ self._set_focus_from_display_widget()
494
+ return key
495
+
496
+ def pack(self, size: tuple[int] | tuple[()] = (), focus: bool = False) -> tuple[int, int]:
497
+ if size:
498
+ return super().pack(size, focus)
499
+ cols = len(self) * self.cell_width + (len(self) - 1) * self.h_sep
500
+ return cols, self.rows((cols,), focus)
501
+
502
+ def rows(self, size: tuple[int], focus: bool = False) -> int:
503
+ self.get_display_widget(size)
504
+ return super().rows(size, focus=focus)
505
+
506
+ def render(self, size: tuple[int] | tuple[()], focus: bool = False):
507
+ self.get_display_widget(size)
508
+ return super().render(size, focus)
509
+
510
+ def get_cursor_coords(self, size: tuple[int] | tuple[()]) -> tuple[int, int]:
511
+ """Get cursor from display widget."""
512
+ self.get_display_widget(size)
513
+ return super().get_cursor_coords(size)
514
+
515
+ def move_cursor_to_coords(self, size: tuple[int] | tuple[()], col: int, row: int):
516
+ """Set the widget in focus based on the col + row."""
517
+ self.get_display_widget(size)
518
+ rval = super().move_cursor_to_coords(size, col, row)
519
+ self._set_focus_from_display_widget()
520
+ return rval
521
+
522
+ def mouse_event(
523
+ self,
524
+ size: tuple[int] | tuple[()],
525
+ event: str,
526
+ button: int,
527
+ col: int,
528
+ row: int,
529
+ focus: bool,
530
+ ) -> Literal[True]:
531
+ self.get_display_widget(size)
532
+ super().mouse_event(size, event, button, col, row, focus)
533
+ self._set_focus_from_display_widget()
534
+ return True # at a minimum we adjusted our focus
535
+
536
+ def get_pref_col(self, size: tuple[int] | tuple[()]):
537
+ """Return pref col from display widget."""
538
+ self.get_display_widget(size)
539
+ return super().get_pref_col(size)