enthought / traitsui
1
# -------------------------------------------------------------------------
2
#
3
#  Copyright (c) 2008, Enthought, Inc.
4
#  All rights reserved.
5
#
6
#  This software is provided without warranty under the terms of the BSD
7
#  license included in LICENSE.txt and may be redistributed only
8
#  under the conditions described in the aforementioned license.  The license
9
#  is also available online at http://www.enthought.com/licenses/BSD.txt
10
#
11
#  Thanks for using Enthought open source!
12
#
13
#  Author: David C. Morrill
14
#  Date:   02/29/2008
15
#
16
# -------------------------------------------------------------------------
17

18 4
""" Defines the adapter classes associated with the Traits UI TabularEditor.
19
"""
20

21

22

23

24

25 4
from traits.api import (
26
    Any,
27
    Bool,
28
    Enum,
29
    Event,
30
    Float,
31
    HasPrivateTraits,
32
    HasTraits,
33
    Instance,
34
    Int,
35
    Interface,
36
    List,
37
    Property,
38
    Str,
39
    Either,
40
    cached_property,
41
    on_trait_change,
42
    provides,
43
)
44

45 4
from .toolkit_traits import Color, Font
46

47

48 4
class ITabularAdapter(Interface):
49

50
    #: The row index of the current item being adapted:
51 4
    row = Int()
52

53
    #: The current column id being adapted (if any):
54 4
    column = Any()
55

56
    #: Current item being adapted:
57 4
    item = Any()
58

59
    #: The current value (if any):
60 4
    value = Any()
61

62
    #: The list of columns the adapter supports. The items in the list have the
63
    #: same format as the :py:attr:`columns` trait in the
64
    #: :py:class:`TabularAdapter` class, with the additional requirement that
65
    #: the ``string`` values must correspond to a ``string`` value in the
66
    #: associated :py:class:`TabularAdapter` class.
67 4
    columns = List(Str)
68

69
    #: Does the adapter know how to handle the current *item* or not:
70 4
    accepts = Bool()
71

72
    #: Does the value of *accepts* depend only upon the type of *item*?
73 4
    is_cacheable = Bool()
74

75

76 4
@provides(ITabularAdapter)
77 4
class AnITabularAdapter(HasPrivateTraits):
78

79
    # Implementation of the ITabularAdapter Interface ------------------------
80

81
    #: The row index of the current item being adapted:
82 4
    row = Int()
83

84
    #: The current column id being adapted (if any):
85 4
    column = Any()
86

87
    #: Current item being adapted:
88 4
    item = Any()
89

90
    #: The current value (if any):
91 4
    value = Any()
92

93
    #: The list of columns the adapter supports. The items in the list have the
94
    #: same format as the :py:attr:`columns` trait in the
95
    #: :py:class:`TabularAdapter` class, with the additional requirement that
96
    #: the ``string`` values must correspond to a ``string`` value in the
97
    #: associated :py:class:`TabularAdapter` class.
98 4
    columns = List(Str)
99

100
    #: Does the adapter know how to handle the current *item* or not:
101 4
    accepts = Bool(True)
102

103
    #: Does the value of *accepts* depend only upon the type of *item*?
104 4
    is_cacheable = Bool(True)
105

106

107 4
class TabularAdapter(HasPrivateTraits):
108
    """ The base class for adapting list items to values that can be edited
109
        by a TabularEditor.
110
    """
111

112
    # -- Public Trait Definitions ---------------------------------------------
113

114
    #: A list of columns that should appear in the table. Each entry can have
115
    #: one of two forms: ``string`` or ``(string, id)``, where ``string`` is
116
    #: the UI name of the column, and ``id`` is a value that identifies that
117
    #: column to the adapter. Normally this value is either a trait name or an
118
    #: index, but it can be any value that the adapter wants. If only
119
    #: ``string`` is specified, then ``id`` is the index of the ``string``
120
    #: within :py:attr:`columns`.
121 4
    columns = List()
122

123
    #: Maps UI name of column to value identifying column to the adapter, if
124
    #: different.
125 4
    column_dict = Property()
126

127
    #: Specifies the default value for a new row.  This will usually need to be
128
    #: overridden.
129 4
    default_value = Any("")
130

131
    #: The default text color for odd table rows.
132 4
    odd_text_color = Color(None, update=True)
133

134
    #: The default text color for even table rows.
135 4
    even_text_color = Color(None, update=True)
136

137
    #: The default text color for table rows.
138 4
    default_text_color = Color(None, update=True)
139

140
    #: The default background color for odd table rows.
141 4
    odd_bg_color = Color(None, update=True)
142

143
    #: The default background color for even table rows.
144 4
    even_bg_color = Color(None, update=True)
145

146
    #: The default background color for table rows.
147 4
    default_bg_color = Color(None, update=True)
148

149
    #: Horizontal alignment to use for a specified column.
150 4
    alignment = Enum("left", "center", "right")
151

152
    #: The Python format string to use for a specified column.
153 4
    format = Str("%s")
154

155
    #: Width of a specified column.
156 4
    width = Float(-1)
157

158
    #: Can the text value of each item be edited?
159 4
    can_edit = Bool(True)
160

161
    #: The value to be dragged for a specified row item.
162 4
    drag = Property()
163

164
    #: Can any arbitrary value be dropped onto the tabular view.
165 4
    can_drop = Bool(False)
166

167
    #: Specifies where a dropped item should be placed in the table relative to
168
    #: the item it is dropped on.
169 4
    dropped = Enum("after", "before")
170

171
    #: The font for a row item.
172 4
    font = Font(None)
173

174
    #: The text color for a row item.
175 4
    text_color = Property()
176

177
    #: The background color for a row item.
178 4
    bg_color = Property()
179

180
    #: The name of the default image to use for column items.
181 4
    image = Str(None, update=True)
182

183
    #: The text of a row/column item.
184 4
    text = Property()
185

186
    #: The content of a row/column item (may be any Python value).
187 4
    content = Property()
188

189
    #: The tooltip information for a row/column item.
190 4
    tooltip = Str()
191

192
    #: The context menu for a row/column item.
193 4
    menu = Any()
194

195
    #: The context menu for column header.
196 4
    column_menu = Any()
197

198
    #: List of optional delegated adapters.
199 4
    adapters = List(ITabularAdapter, update=True)
200

201
    # -- Traits Set by the Editor ---------------------------------------------
202

203
    #: The object whose trait is being edited.
204 4
    object = Instance(HasTraits)
205

206
    #: The name of the trait being edited.
207 4
    name = Str()
208

209
    #: The row index of the current item being adapted.
210 4
    row = Int()
211

212
    #: The column index of the current item being adapted.
213 4
    column = Int()
214

215
    #: The current column id being adapted (if any).
216 4
    column_id = Any()
217

218
    #: Current item being adapted.
219 4
    item = Any()
220

221
    #: The current value (if any).
222 4
    value = Any()
223

224
    # -- Private Trait Definitions --------------------------------------------
225

226
    #: Cache of attribute handlers.
227 4
    cache = Any({})
228

229
    #: Event fired when the cache is flushed.
230 4
    cache_flushed = Event(update=True)
231

232
    #: The mapping from column indices to column identifiers (defined by the
233
    #: :py:attr:`columns` trait).
234 4
    column_map = Property(depends_on="columns")
235

236
    #: The mapping from column indices to column labels (defined by the
237
    #: :py:attr:`columns` trait).
238 4
    label_map = Property(depends_on="columns")
239

240
    #: The name of the trait on a row item containing the value to use
241
    #: as a row label. If ``None``, the label will be the empty string.
242 4
    row_label_name = Either(None, Str)
243

244
    #: For each adapter, specifies the column indices the adapter handles.
245 4
    adapter_column_indices = Property(depends_on="adapters,columns")
246

247
    #: For each adapter, specifies the mapping from column index to column id.
248 4
    adapter_column_map = Property(depends_on="adapters,columns")
249

250
    # -------------------------------------------------------------------------
251
    # TabularAdapter interface
252
    # -------------------------------------------------------------------------
253

254 4
    def cleanup(self):
255
        """ Clean up the adapter to remove references to objects.
256
        """
257 4
        self.trait_setq(object=None, item=None, value=None)
258

259
    # -- Adapter methods that are sensitive to item type ----------------------
260

261 4
    def get_alignment(self, object, trait, column):
262
        """ Returns the alignment style to use for a specified column.
263

264
        The possible values that can be returned are: ``'left'``, ``'center'``
265
        or ``'right'``. All table items share the same alignment for a
266
        specified column.
267
        """
268 4
        return self._result_for("get_alignment", object, trait, 0, column)
269

270 4
    def get_width(self, object, trait, column):
271
        """ Returns the width to use for a specified column.
272

273
        If the value is <= 0, the column will have a *default* width, which is
274
        the same as specifying a width of 0.1.
275

276
        If the value is > 1.0, it is converted to an integer and the result is
277
        the width of the column in pixels. This is referred to as a
278
        *fixed width* column.
279

280
        If the value is a float such that 0.0 < value <= 1.0, it is treated as
281
        the *unnormalized fraction of the available space* that is to be
282
        assigned to the column. What this means requires a little explanation.
283

284
        To arrive at the size in pixels of the column at any given time, the
285
        editor adds together all of the *unnormalized fraction* values
286
        returned for all columns in the table to arrive at a total value. Each
287
        *unnormalized fraction* is then divided by the total to create a
288
        *normalized fraction*. Each column is then assigned an amount of space
289
        in pixels equal to the maximum of 30 or its *normalized fraction*
290
        multiplied by the *available space*. The *available space* is defined
291
        as the actual width of the table minus the width of all *fixed width*
292
        columns. Note that this calculation is performed each time the table is
293
        resized in the user interface, thus allowing columns of this type to
294
        increase or decrease their width dynamically, while leaving *fixed
295
        width* columns unchanged.
296
        """
297 4
        return self._result_for("get_width", object, trait, 0, column)
298

299 4
    def get_can_edit(self, object, trait, row):
300
        """ Returns whether the user can edit a specified row.
301

302
        A ``True`` result indicates that the value can be edited, while a
303
        ``False`` result indicates that it cannot.
304
        """
305 4
        return self._result_for("get_can_edit", object, trait, row, 0)
306

307 4
    def get_drag(self, object, trait, row):
308
        """ Returns the value to be *dragged* for a specified row.
309

310
        A result of ``None`` means that the item cannot be dragged. Note that
311
        the value returned does not have to be the actual row item. It can be
312
        any value that you want to drag in its place. In particular, if you
313
        want the drag target to receive a copy of the row item, you should
314
        return a copy or clone of the item in its place.
315

316
        Also note that if multiple items are being dragged, and this method
317
        returns ``None`` for any item in the set, no drag operation is
318
        performed.
319
        """
320 4
        return self._result_for("get_drag", object, trait, row, 0)
321

322 4
    def get_can_drop(self, object, trait, row, value):
323
        """ Returns whether the specified ``value`` can be dropped on the specified row.
324

325
        A value of ``True`` means the ``value`` can be dropped; and a value of
326
        ``False`` indicates that it cannot be dropped.
327

328
        The result is used to provide the user positive or negative drag
329
        feedback while dragging items over the table. ``value`` will always be
330
        a single value, even if multiple items are being dragged. The editor
331
        handles multiple drag items by making a separate call to
332
        :py:meth:`get_can_drop` for each item being dragged.
333
        """
334 4
        return self._result_for("get_can_drop", object, trait, row, 0, value)
335

336 4
    def get_dropped(self, object, trait, row, value):
337
        """ Returns how to handle a specified ``value`` being dropped on a specified row.
338

339
        The possible return values are:
340

341
        - ``'before'``: Insert the specified ``value`` before the dropped on item.
342
        - ``'after'``: Insert the specified ``value`` after the dropped on item.
343

344
        Note there is no result indicating *do not drop* since you will have
345
        already indicated that the ``object`` can be dropped by the result
346
        returned from a previous call to :py:meth:`get_can_drop`.
347
        """
348 4
        return self._result_for("get_dropped", object, trait, row, 0, value)
349

350 4
    def get_font(self, object, trait, row, column=0):
351
        """ Returns the font to use for displaying a specified row or cell.
352

353
        A result of ``None`` means use the default font; otherwise a toolkit
354
        font object should be returned. Note that all columns for the specified
355
        table row will use the font value returned.
356
        """
357 4
        return self._result_for("get_font", object, trait, row, column)
358

359 4
    def get_text_color(self, object, trait, row, column=0):
360
        """ Returns the text color to use for a specified row or cell.
361

362
        A result of ``None`` means use the default text color; otherwise a
363
        toolkit-compatible color should be returned. Note that all columns for
364
        the specified table row will use the text color value returned.
365
        """
366 4
        return self._result_for("get_text_color", object, trait, row, column)
367

368 4
    def get_bg_color(self, object, trait, row, column=0):
369
        """ Returns the background color to use for a specified row or cell.
370

371
        A result of ``None`` means use the default background color; otherwise
372
        a toolkit-compatible color should be returned. Note that all columns
373
        for the specified table row will use the background color value
374
        returned.
375
        """
376 4
        return self._result_for("get_bg_color", object, trait, row, column)
377

378 4
    def get_image(self, object, trait, row, column):
379
        """ Returns the image to display for a specified cell.
380

381
        A result of ``None`` means no image will be displayed in the specified
382
        table cell. Otherwise the result should either be the name of the
383
        image, or an :py:class:`~pyface.image_resource.ImageResource` object
384
        specifying the image to display.
385

386
        A name is allowed in the case where the image is specified in the
387
        :py:class:`~traitsui.editors.tabular_editor.TabularEditor`
388
        :py:attr:`~traitsui.editors.tabular_editor.TabularEditor.images` trait.
389
        In that case, the name should be the same as the string specified in
390
        the :py:class:`~pyface.image_resource.ImageResource` constructor.
391
        """
392 4
        return self._result_for("get_image", object, trait, row, column)
393

394 4
    def get_format(self, object, trait, row, column):
395
        """ Returns the Python formatting string to apply to the specified cell.
396

397
        The resulting of formatting with this string will be used as the text
398
        to display it in the table.
399

400
        The return can be any Python string containing exactly one old-style
401
        Python formatting sequence, such as ``'%.4f'`` or ``'(%5.2f)'``.
402
        """
403 4
        return self._result_for("get_format", object, trait, row, column)
404

405 4
    def get_text(self, object, trait, row, column):
406
        """ Returns a string containing the text to display for a specified cell.
407

408
        If the underlying data representation for a specified item is not a
409
        string, then it is your responsibility to convert it to one before
410
        returning it as the result.
411
        """
412 4
        return self._result_for("get_text", object, trait, row, column)
413

414 4
    def get_content(self, object, trait, row, column):
415
        """ Returns the content to display for a specified cell.
416
        """
417 4
        return self._result_for("get_content", object, trait, row, column)
418

419 4
    def set_text(self, object, trait, row, column, text):
420
        """ Sets the value for the specified cell.
421

422
        This method is called when the user completes an editing operation on a
423
        table cell.
424

425
        The string specified by ``text`` is the value that the user has
426
        entered in the table cell.  If the underlying data does not store the
427
        value as text, it is your responsibility to convert ``text`` to the
428
        correct representation used.
429
        """
430 4
        self._result_for("set_text", object, trait, row, column, text)
431

432 4
    def get_tooltip(self, object, trait, row, column):
433
        """ Returns a string containing the tooltip to display for a specified cell.
434

435
        You should return the empty string if you do not wish to display a
436
        tooltip.
437
        """
438 0
        return self._result_for("get_tooltip", object, trait, row, column)
439

440 4
    def get_menu(self, object, trait, row, column):
441
        """ Returns the context menu for a specified cell.
442
        """
443 0
        return self._result_for("get_menu", object, trait, row, column)
444

445 4
    def get_column_menu(self, object, trait, row, column):
446
        """ Returns the context menu for a specified column.
447
        """
448 0
        return self._result_for("get_column_menu", object, trait, row, column)
449

450
    # -- Adapter methods that are not sensitive to item type ------------------
451

452 4
    def get_item(self, object, trait, row):
453
        """ Returns the specified row item.
454

455
        The value returned should be the value that exists (or *logically*
456
        exists) at the specified ``row`` in your data. If your data is not
457
        really a list or array, then you can just use ``row`` as an integer
458
        *key* or *token* that can be used to retrieve a corresponding item. The
459
        value of ``row`` will always be in the range: 0 <= row <
460
        ``len(object, trait)`` (i.e. the result returned by the adapter
461
        :py:meth:`len` method).
462

463
        The default implementation assumes the trait defined by
464
        ``object.trait`` is a *sequence* and attempts to return the value at
465
        index ``row``. If an error occurs, it returns ``None`` instead. This
466
        definition should work correctly for lists, tuples and arrays, or any
467
        other object that is indexable, but will have to be overridden for all
468
        other cases.
469
        """
470 4
        try:
471 4
            return getattr(object, trait)[row]
472 0
        except:
473 0
            return None
474

475 4
    def len(self, object, trait):
476
        """ Returns the number of row items in the specified ``object.trait``.
477

478
        The result should be an integer greater than or equal to 0.
479

480
        The default implementation assumes the trait defined by
481
        ``object.trait`` is a *sequence* and attempts to return the result of
482
        calling ``len(object.trait)``. It will need to be overridden for any
483
        type of data which for which :py:func:`len` will not work.
484
        """
485
        # Sometimes, during shutdown, the object has been set to None.
486 4
        if object is None:
487 4
            return 0
488
        else:
489 4
            return len(getattr(object, trait))
490

491 4
    def get_default_value(self, object, trait):
492
        """ Returns a new default value for the specified ``object.trait`` list.
493

494
        This method is called when *insert* or *append* operations are allowed
495
        and the user requests that a new item be added to the table. The result
496
        should be a new instance of whatever underlying representation is being
497
        used for table items.
498

499
        The default implementation simply returns the value of the adapter's
500
        :py:attr:`default_value` trait.
501
        """
502 0
        return self.default_value
503

504 4
    def delete(self, object, trait, row):
505
        """ Deletes the specified row item.
506

507
        This method is only called if the *delete* operation is specified in
508
        the :py:class:`~traitsui.editors.tabular_editor.TabularEditor`
509
        :py:attr:`~traitsui.editors.tabular_editor.TabularEditor.operation`
510
        trait, and the user requests that the item be deleted from the table.
511

512
        The adapter can still choose not to delete the specified item if
513
        desired, although that may prove confusing to the user.
514

515
        The default implementation assumes the trait defined by
516
        ``object.trait`` is a mutable sequence and attempts to perform a
517
        ``del object.trait[row]`` operation.
518
        """
519 4
        del getattr(object, trait)[row]
520

521 4
    def insert(self, object, trait, row, value):
522
        """ Inserts ``value`` at the specified ``object.trait[row]`` index.
523

524
        The specified ``value`` can be:
525

526
        - An item being moved from one location in the data to another.
527
        - A new item created by a previous call to
528
          :py:meth:`~TabularAdapter.get_default_value`.
529
        - An item the adapter previously approved via a call to
530
          :py:meth:`~TabularAdapter.get_can_drop`.
531

532
        The adapter can still choose not to insert the item into the data,
533
        although that may prove confusing to the user.
534

535
        The default implementation assumes the trait defined by
536
        ``object.trait`` is a mutable sequence and attempts to perform an
537
        ``object.trait[row:row] = [value]`` operation.
538
        """
539 4
        getattr(object, trait)[row:row] = [value]
540

541 4
    def get_column(self, object, trait, index):
542
        """ Returns the column id corresponding to a specified column index.
543
        """
544 0
        self.object, self.name = object, trait
545 0
        return self.column_map[index]
546

547
    # -- Property Implementations ---------------------------------------------
548

549 4
    def _get_drag(self):
550 4
        return self.item
551

552 4
    def _get_text_color(self):
553 4
        if (self.row % 2) == 1:
554 4
            return self.even_text_color_ or self.default_text_color
555

556 4
        return self.odd_text_color or self.default_text_color_
557

558 4
    def _get_bg_color(self):
559 4
        if (self.row % 2) == 1:
560 4
            return self.even_bg_color_ or self.default_bg_color_
561

562 4
        return self.odd_bg_color or self.default_bg_color_
563

564 4
    def _get_text(self):
565 4
        return self.get_format(
566
            self.object, self.name, self.row, self.column
567
        ) % self.get_content(self.object, self.name, self.row, self.column)
568

569 4
    def _set_text(self, value):
570 4
        if isinstance(self.column_id, int):
571 0
            self.item[self.column_id] = self.value
572
        else:
573
            # Convert value to the correct trait type.
574 0
            try:
575 0
                trait_handler = self.item.trait(self.column_id).handler
576 0
                setattr(
577
                    self.item,
578
                    self.column_id,
579
                    trait_handler.evaluate(self.value),
580
                )
581 0
            except:
582 0
                setattr(self.item, self.column_id, value)
583

584 4
    def _get_content(self):
585 4
        if isinstance(self.column_id, int):
586 3
            return self.item[self.column_id]
587

588 4
        return getattr(self.item, self.column_id)
589

590
    # -- Property Implementations ---------------------------------------------
591

592 4
    @cached_property
593
    def _get_column_dict(self):
594 0
        cols = {}
595 4
        for i, value in enumerate(self.columns):
596 4
            if isinstance(value, str):
597 0
                cols.update({value: value})
598
            else:
599 0
                cols.update({value[0]: value[1]})
600 0
        return cols
601

602 4
    @cached_property
603
    def _get_column_map(self):
604 4
        map = []
605 4
        for i, value in enumerate(self.columns):
606 4
            if isinstance(value, str):
607 4
                map.append(i)
608
            else:
609 4
                map.append(value[1])
610

611 4
        return map
612

613 4
    def get_label(self, section, obj=None):
614
        """Override this method if labels will vary from object to object."""
615 4
        return self.label_map[section]
616

617 4
    def get_row_label(self, section, obj=None):
618 4
        if self.row_label_name is None:
619 4
            return None
620 0
        rows = getattr(obj, self.name, None)
621 4
        if rows is None:
622 0
            return None
623 0
        item = rows[section]
624 0
        return getattr(item, self.row_label_name, None)
625

626 4
    @cached_property
627
    def _get_label_map(self):
628 4
        map = []
629 4
        for i, value in enumerate(self.columns):
630 4
            if isinstance(value, str):
631 4
                map.append(value)
632
            else:
633 4
                map.append(value[0])
634

635 4
        return map
636

637 4
    @cached_property
638
    def _get_adapter_column_indices(self):
639 0
        labels = self.label_map
640 0
        map = []
641 4
        for adapter in self.adapters:
642 0
            indices = []
643 4
            for label in adapter.columns:
644 4
                if not isinstance(label, str):
645 0
                    label = label[0]
646

647 0
                indices.append(labels.index(label))
648 0
            map.append(indices)
649 0
        return map
650

651 4
    @cached_property
652
    def _get_adapter_column_map(self):
653 0
        labels = self.label_map
654 0
        map = []
655 4
        for adapter in self.adapters:
656 0
            mapping = {}
657 4
            for label in adapter.columns:
658 0
                id = None
659 4
                if not isinstance(label, str):
660 0
                    label, id = label
661

662 0
                key = labels.index(label)
663 4
                if id is None:
664 0
                    id = key
665

666 0
                mapping[key] = id
667

668 0
            map.append(mapping)
669

670 0
        return map
671

672
    # -- Private Methods ------------------------------------------------------
673

674 4
    def _result_for(self, name, object, trait, row, column, value=None):
675
        """ Returns/Sets the value of the specified *name* attribute for the
676
            specified *object.trait[row].column* item.
677
        """
678 4
        self.object = object
679 4
        self.name = trait
680 4
        self.row = row
681 4
        self.column = column
682 4
        self.column_id = column_id = self.column_map[column]
683 4
        self.value = value
684 4
        self.item = item = self.get_item(object, trait, row)
685 4
        item_class = item.__class__
686 4
        key = "%s:%s:%d" % (item_class.__name__, name, column)
687 4
        handler = self.cache.get(key)
688 4
        if handler is not None:
689 4
            return handler()
690

691 4
        prefix = name[:4]
692 4
        trait_name = name[4:]
693

694 4
        for i, adapter in enumerate(self.adapters):
695 4
            if column in self.adapter_column_indices[i]:
696 0
                adapter.row = row
697 0
                adapter.item = item
698 0
                adapter.value = value
699 0
                adapter.column = column_id = self.adapter_column_map[i][column]
700 4
                if adapter.accepts:
701 0
                    get_name = "%s_%s" % (column_id, trait_name)
702 4
                    if adapter.trait(get_name) is not None:
703 4
                        if prefix == "get_":
704 4
                            handler = lambda: getattr(
705
                                adapter.trait_set(
706
                                    row=self.row,
707
                                    column=column_id,
708
                                    item=self.item,
709
                                ),
710
                                get_name,
711
                            )
712
                        else:
713 4
                            handler = lambda: setattr(
714
                                adapter.trait_set(
715
                                    row=self.row,
716
                                    column=column_id,
717
                                    item=self.item,
718
                                ),
719
                                get_name,
720
                                self.value,
721
                            )
722

723 4
                        if adapter.is_cacheable:
724 0
                            break
725

726 0
                        return handler()
727
        else:
728 4
            if item is not None and hasattr(item_class, "__mro__"):
729 4
                for klass in item_class.__mro__:
730 4
                    handler = self._get_handler_for(
731
                        "%s_%s_%s" % (klass.__name__, column_id, trait_name),
732
                        prefix,
733
                    ) or self._get_handler_for(
734
                        "%s_%s" % (klass.__name__, trait_name), prefix
735
                    )
736 4
                    if handler is not None:
737 0
                        break
738

739 4
            if handler is None:
740 4
                handler = self._get_handler_for(
741
                    "%s_%s" % (column_id, trait_name), prefix
742
                ) or self._get_handler_for(trait_name, prefix)
743

744 4
        self.cache[key] = handler
745 4
        return handler()
746

747 4
    def _get_handler_for(self, name, prefix):
748
        """ Returns the handler for a specified trait name (or None if not
749
            found).
750
        """
751 4
        if self.trait(name) is not None:
752 4
            if prefix == "get_":
753 4
                return lambda: getattr(self, name)
754

755 4
            return lambda: setattr(self, name, self.value)
756

757 4
        return None
758

759 4
    @on_trait_change("columns,adapters.+update")
760
    def _flush_cache(self):
761
        """ Flushes the cache when the columns or any trait on any adapter
762
            changes.
763
        """
764 4
        self.cache = {}
765 4
        self.cache_flushed = True

Read our documentation on viewing source code .

Loading