.. _advanced-editor-adapters: ======================== Advanced Editor Adapters ======================== A number of trait editors provide a way for code to adapt objects to the expected API for the editor, and this can be used by Traits UI code to provide strongly customized views of the data. The editors which provide this facility are the ListStrEditor, the TabularEditor and the TreeEditor. In this section we will look more closely at each of these and discuss how they can be customized as needed. .. _advanced-tree-node: The TreeEditor and TreeNodes ============================ .. _tree-nodes: .. module:: traitsui.tree_node :noindex: The :py:class:`~traitsui.editors.tree_editor.TreeEditor` internally associates with each node in the tree a pair consisting of the object that is associated with the node and something that to adheres to the :py:class:`TreeNode` interface. The :py:class:`TreeNode` interface is not explicitly laid out, but it corresponds to the "overridable" public methods of the :py:class:`TreeNode` class, such as :py:meth:`~TreeNode.get_label` and :py:meth:`~TreeNode.get_children`. This means that the tree editor expects one of the following three things to be offered as values associated with a node, such as the value of the root node trait or values that might be returned by the :py:meth:`~TreeNode.get_children` method: - an explicit pair of the object and a :py:class:`TreeNode` instance for that object - an object that has :py:meth:`~TreeNode.is_node_for` return ``True`` for at least one of the factory's :py:attr:`~traitsui.editors.tree_editor.TreeEditor.nodes` items. - an object that provides or can be adapted to the :py:class:`ITreeNode` interface using Traits adaptation. There is a crucial distinction between the way that :py:class:`TreeNode` and :py:class:`ITreeNode` work. :py:class:`TreeNode` is generic---it is designed to work with certain types of objects, but doesn't hold references to those objects---instead they rely on the :py:class:`TreeEditor` to keep track of the association between the objects and the :py:class:`TreeNode` to use with that object. :py:class:`ITreeNode`, on the other hand, is an interface and uses adapters associated with individual objects rather than types of objects. This means that :py:class:`ITreeNode`-based approaches are generally more heavyweight: you end up with at least one additional class instance for each displayed node (and most likely two additional instances) vs. a tuple. On the other hand, because :py:class:`ITreeNode` uses Traits adaptation, you can extend the set of classes that are supported by adding more :py:class:`ITreeNode` adapters, for example via Envisage extension points. Specializing TreeNode Behaviour ------------------------------- In general using :py:class:`TreeNode` s works well when you have a hierarchy of :py:class:`~traits.api.HasTraits` objects, which is probably the most common situation. And while the :py:class:`TreeNode` is fairly generic, there are times when you want to override the default behaviour of one or more aspects of the object. In this case it may be that the best way to do this is to simply subclass :py:class:`TreeNode` and adjust it to behave the way that you want. For example, the default behaviour of the :py:class:`TreeNode` is to show one of 3 different icons depending on whether the node has children or not and whether it has been expanded. But you might want to display a different icon based on some attribute of the object being viewed, and that would require a new :py:class:`TreeNode` subclass to override that behaviour. Concretely, if we had different document types, identified by file extension:: class DocumentTreeNode(TreeNode): icons = Dict({ '.npy': ImageResource('document-table'), '.txt': ImageResource('document-text'), '.rst': ImageResource('document-text'), '.png': ImageResource('document-image'), '.jpg': ImageResource('document-image'), }) def get_icon(self, object, is_expanded): icon = self.icons.get(object.extension, self.icon_item) return icon This :py:class:`TreeNode` subclass can now be used with any compatible class to give a richer set of icons. Common use cases for this approach would include: - more customized icon display, as above. - having the label built from multiple traits, which requires overriding :py:meth:`~TreeNode.get_label`, :py:meth:`~TreeNode.when_label_changed` and possibly :py:meth:`~TreeNode.set_label`. - having the children come from multiple traits, which requires overriding :py:meth:`~TreeNode.allows_children`, :py:meth:`~TreeNode.get_children`, :py:meth:`~TreeNode.when_children_replaced`, :py:meth:`~TreeNode.when_children_changed` and possibly :py:meth:`~TreeNode.append_child`, :py:meth:`~TreeNode.insert_child` and :py:meth:`~TreeNode.delete_child` (although there may be better ways to handle this situation by using multiple :py:class:`TreeNodes` for the class). - being more selective about what objects to use for the node. For example, requiring not only that an object be of a certain class, but that it also have an attribute with a cetain value. This requires overriding :py:meth:`~TreeNode.is_node_for`. - customization of menus on a per-object basis, or other UI behaviour like drag and drop, selection and clicking. This has the advantage that most of the time the behaviour that you want is built into the :py:class:`TreeNode` class, and you only need to change the things which are not to your requirements. Where :py:class:`TreeNode` classes are generally weak is when the object you are trying to view is not a :py:class:`~traits.api.HasTraits` instance, or where you don't know the full set of classes that you need to display in the tree when writing the UI. You can overcome these obstacles by careful subclassing, taking particular care to avoid things like trying to set traits listeners on non-:py:class:`~traits.api.HasTraits` objects or adapting the object to a desired interface before using it. But in these cases it may be better to use a different approach. ITreeNodes and ITreeNodeAdapters -------------------------------- These are most useful for situations where you don't know the full set of classes that may be displayed in a tree. This is a common situation when writing complex applications using libraries like Envisage that allow new functionality to be added to the application via plug-ins (potentially during run-time!). It is also useful in situations where the model object that is being viewed isn't a :py:class:`~traits.api.HasTraits` object, or where you may need some UI state in the node that doesn't belong on the underlying model object (for example, caching quantities which are expensive to compute). Before using this approach, you should make sure that you understand the way that traits adaptation works. To make writing code which satisfies the :py:class:`ITreeNode` interface easier, there is an :py:class:`ITreeNodeAdapter` class which provides basic functionality and which can be subclassed to provide an adapter class for your own nodes. This adapter is minimalistic and not complete. You will at a minimum need to override the :py:meth:`~ITreeNodeAdapter.get_label` method, and probably many others to get the desired behaviour. Since the :py:class:`ITreeNodeAdapter` is an :py:class:`Adapter` subclass, the object being adapted is available as the :py:attr:`adaptee` attribute. This means that the methods might look similar to the ones for :py:class:`TreeNode`, but they don't expect to be passed the object as a parameter. Once you have written the :py:class:`ITreeNodeAdapter` subclass, you have to register the adapter with traits using the Traits :py:func:`regsiter_factory` function. You are not required to use :py:class:`ITreeNodeAdapter` if you don't wish to. You can instead write a class which ``@provides`` the :py:class:`ITreeNode` interface directly, or create an alternative adapter class. Note that currently the tree editor infrastructure uses the deprecated Traits :py:func:`adapts` class advisor and the default traits adapter registry which means that you can't have mulitple different :py:class:`ITreeNode` adapters for a given object to use in different editors within a given application. This is likely to be fixed in a future release of TraitsUI. In the mean-time you can work around this somewhat by having the trait being edited and/or the :py:meth:`~ITreeNodeAdapter.get_children` method return pre-adapted objects, rather than relying on traits adaptation machinery to find and adapt the object. ObjectTreeNodes and TreeNodeObjects ----------------------------------- Another approach to adapting objects, particularly non-:py:class:`HasTraits` objects is used by the :py:class:`ValueEditor`, but is available for general tree editors to use as well. In this approach you write one or more :py:class:`TreeNodeObject` classes that wrap the model objects that you want to display, and then use instances of the :py:class:`TreeNodeObject` classes within the tree editor, both as the root node being edited, and the objects returned by the :py:meth:`tno_get_children` methods. To fit these with the expected :py:class:`TreeNode` classes used by the :py:class:`TreeEditor`, there is the :py:class:`ObjectTreeNode` class which knows how to call the appropriate :py:class:`TreeNodeObjects` and which can be given a list of :py:class:`TreeNodeObject` classes that it understands. For example, it is possible to represent a tree structure in Python using nested dictionaries with strings as keys. A :py:class:`TreeNodeObject` for such a structure might look like this:: class DictNode(TreeNodeObject): #: The parent of the node parent = Instance('DictNode') #: The label for the node label = Str() #: The value for this node value = Any() def tno_get_label(self, node): return self.label def tno_allows_children(self, node): return isinstance(self.value, dict) def tno_has_children(self, node): return bool(self.value) def tno_get_children(self, node): return [ DictNode(parent=self, label=key, value=value) for key, value in sorted(self.value.items()) ] and so forth. There is additional work if you want to be able to modify the structure of the tree, for example. In addition to defining the :py:class:`TreeNodeObject` subclass, you also need provide the nodes for the editor something like this:: dict_tree_editor = TreeEditor( editable=False, nodes=[ ObjectTreeNode( node_for=[DictNode], rename=False, rename_me=False, copy=False, delete=False, delete_me=False, ) ] ) The :py:class:`ObjectTreeNode` is a :py:class:`TreeNode` subclass that delegates operations to the :py:class:`TreeNodeObject`, but the default :py:class:`TreeNodeObject` methods try to behave in the same way as the base :py:class:`TreeNode`, so you can specify global behaviour on the :py:class:`ObjectTreeNode` in the same way that you can for a :py:class:`TreeNode`. The last piece to make this approach work is that the root node when editing has to be a :py:class:`DictNode` instance, so you may need to provide a property that wraps the raw tree structure in a :py:class:`DictNode` to get started: unlike the :py:class:`ITreeNodeAdapter` approaches this wrapping not automatically provided for you. Custom Renderers ---------------- The Qt backend allows users to completely override the rendering of cells in a TreeEditor. To do this, the TreeNode should override the :py:meth:`TreeNode.get_renderer` method to return an instance of a subclass of :py:class:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer`. A :py:class:`~traitsui.qt.tree_node_renderers.WordWrapRenderer` is available to provide basic word-wrapped layout in a cell, but user-defined subclasses can do any rendering that they want by implementing their own :py:class:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer` subclass. :py:class:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer` is an abstract base class, and subclasses must implement two methods: :py:meth:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer.size` A method which should return a (height, width) tuple giving the preferred size for the cell. Depending on other constraints and user interactions, this may not be the actual size that the cell will have available. The toolkit will provide a ``size_context`` object that provides useful parameters to help with sizing operations. In the Qt backend, this is a tuple containing the arguments passed to the Qt :py:meth:`sizeHint` method of a :py:class:`QStyledItemDelegate`. :py:meth:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer.paint` Toolkit-dependent code that renders the cell The toolkit will provide a ```paint_context``` object that provides useful parameters to help with painting operations. In the Qt backend, this is a tuple containing the arguments passed to the Qt :py:meth:`paint` method of a :py:class:`QStyledItemDelegate`. In particular, the first argument is always a :py:class:`QPainter` instance and the second a :py:class:`QStyleOptionViewItem` from which you can get the rectangle of the cell being rendered as well as style and font information. The renderer can choose to not handle all of the rendering, and instead let the tree editor handle rendering the icon or the text of the cell, by setting the :py:meth:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer.handles_icon`, :py:meth:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer.handles_text`, and :py:meth:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer.handles_all` traits appropriately. Lastly there is a convenience method :py:meth:`~traitsui.tree_node_renderer.AbstractTreeNodeRenderer.get_label` that gets the label text given the tree node, the underlying object, and the column, smoothing over the TreeNode columns label API. Examples -------- There are a number of examples of use of the :py:class:`~traitsui.editors.tree_editor.TreeEditor` in the TraitsUI demos: - :github-demo:`TreeEditor ` - :github-demo:`Adapted TreeEditor ` - :github-demo:`HDF5 Tree ` - :github-demo:`Tree Editor with Renderer ` .. _advanced-tabular-adapter: The TabularAdapter Class ======================== .. module:: traitsui.tabular_adapter :noindex: The power and flexibility of the tabular editor is mostly a result of the :py:class:`TabularAdapter` class, which is the base class from which all tabular editor adapters must be derived. The :py:class:`~traitsui.editors.tabular_editor.TabularEditor` object interfaces between the underlying toolkit widget and your program, while the :py:class:`TabularAdapter` object associated with the editor interfaces between the editor and your data. The design of the :py:class:`TabularAdapter` base class is such that it tries to make simple cases simple and complex cases possible. How it accomplishes this is what we'll be discussing in the following sections. The TabularAdapter *columns* Trait ---------------------------------- First up is the :py:class:`TabularAdapter` :py:attr:`columns` trait, which is a list of values which define, in presentation order, the set of columns to be displayed by the associated :py:class:`~traitsui.editors.tabular_editor.TabularEditor`. Each entry in the :py:attr:`~TabularAdapter.columns` list can have one of two forms: - ``string`` - ``(string, id)`` where ``string`` is the user interface name of the column (which will appear in the table column header) and ``id`` is any value that you want to use to identify that column to your adapter. Normally this value is either a trait name or an integer index value, but it can be any value you want. If only ``string`` is specified, then ``id`` is the index of the ``string`` within ``columns``. For example, say you want to display a table containing a list of tuples, each of which has three values: a name, an age, and a weight. You could then use the following value for the :py:attr:`~TabularAdapter.columns` trait:: columns = ['Name', 'Age', 'Weight'] By default, the ``id`` values (also referred to in later sections as the *column ids*) for the columns will be the corresponding tuple index values. Say instead that you have a list of :py:class:`Person` objects, with :py:attr:`name`, :py:attr:`age` and :py:attr:`weight` traits that you want to display in the table. Then you could use the following :py:attr:`~TabularAdapter.columns` value instead:: columns = [ ('Name', 'name'), ('Age', 'age'), ('Weight', 'weight'), ] In this case, the *column ids* are the names of the traits you want to display in each column. Note that it is possible to dynamically modify the contents of the :py:attr:`~TabularAdapter.columns` trait while the :py:class:`~traitsui.editors.tabular_editor.TabularEditor` is active. The :py:class:`~traitsui.editors.tabular_editor.TabularEditor` will automatically modify the table to show the new set of defined columns. The Core TabularAdapter Interface --------------------------------- In this section, we'll describe the core interface to the :py:class:`TabularAdapter` class. This is the actual interface used by the :py:class:`~traitsui.editors.tabular_editor.TabularEditor` to access your data and display attributes. In the most complex data representation cases, these are the methods that you must override in order to have the greatest control over what the editor sees and does. However, the base :py:class:`TabularAdapter` class provides default implementations for all of these methods. In subsequent sections, we'll look at how these default implementations provide simple means of customizing the adapter to your needs. But for now, let's start by covering the details of the core interface itself. To reduce the amount of repetition, we'll use the following definitions in all of the method argument lists that follow in this section: object The object whose trait is being edited by the :py:class:`~traitsui.editors.tabular_editor.TabularEditor`. trait The name of the trait the :py:class:`~traitsui.editors.tabular_editor.TabularEditor` is editing. row The row index (starting with 0) of a table item. column The column index (starting with 0) of a table column. The adapter interface consists of a number of methods which can be divided into two main categories: those which are sensitive to the type of a particular table item, and those which are not. We'll begin with the methods that are sensitive to an item's type: :py:meth:`~TabularAdapter.get_alignment` Returns the alignment style to use for a specified column. The possible values that can be returned are: ``'left'``, ``'center'`` or ``'right'``. All table items share the same alignment for a specified column. :py:meth:`~TabularAdapter.get_width` Returns the width to use for a specified column. If the value is <= 0, the column will have a *default* width, which is the same as specifying a width of 0.1. If the value is > 1.0, it is converted to an integer and the result is the width of the column in pixels. This is referred to as a *fixed width* column. If the value is a float such that 0.0 < value <= 1.0, it is treated as the *unnormalized fraction of the available space* that is to be assigned to the column. What this means requires a little explanation. To arrive at the size in pixels of the column at any given time, the editor adds together all of the *unnormalized fraction* values returned for all columns in the table to arrive at a total value. Each *unnormalized fraction* is then divided by the total to create a *normalized fraction*. Each column is then assigned an amount of space in pixels equal to the maximum of 30 or its *normalized fraction* multiplied by the *available space*. The *available space* is defined as the actual width of the table minus the width of all *fixed width* columns. Note that this calculation is performed each time the table is resized in the user interface, thus allowing columns of this type to increase or decrease their width dynamically, while leaving *fixed width* columns unchanged. :py:meth:`~TabularAdapter.get_can_edit` Returns whether the user can edit a specified row. A ``True`` result indicates that the value can be edited, while a ``False`` result indicates that it cannot. :py:meth:`~TabularAdapter.get_drag` Returns the value to be *dragged* for a specified row. A result of ``None`` means that the item cannot be dragged. Note that the value returned does not have to be the actual row item. It can be any value that you want to drag in its place. In particular, if you want the drag target to receive a copy of the row item, you should return a copy or clone of the item in its place. Also note that if multiple items are being dragged, and this method returns ``None`` for any item in the set, no drag operation is performed. :py:meth:`~TabularAdapter.get_can_drop` Returns whether the specified ``value`` can be dropped on the specified row. A value of ``True`` means the ``value`` can be dropped; and a value of ``False`` indicates that it cannot be dropped. The result is used to provide the user positive or negative drag feedback while dragging items over the table. ``value`` will always be a single value, even if multiple items are being dragged. The editor handles multiple drag items by making a separate call to :py:meth:`get_can_drop` for each item being dragged. :py:meth:`~TabularAdapter.get_dropped` Returns how to handle a specified ``value`` being dropped on a specified row. The possible return values are: - ``'before'``: Insert the specified ``value`` before the dropped on item. - ``'after'``: Insert the specified ``value`` after the dropped on item. Note there is no result indicating *do not drop* since you will have already indicated that the ``object`` can be dropped by the result returned from a previous call to :py:meth:`get_can_drop`. :py:meth:`~TabularAdapter.get_font` Returns the font to use for displaying a specified row or cell. A result of ``None`` means use the default font; otherwise a toolkit font object should be returned. Note that all columns for the specified table row will use the font value returned. :py:meth:`~TabularAdapter.get_text_color` Returns the text color to use for a specified row or cell. A result of ``None`` means use the default text color; otherwise a toolkit-compatible color should be returned. Note that all columns for the specified table row will use the text color value returned. :py:meth:`~TabularAdapter.get_bg_color` Returns the background color to use for a specified row or cell. A result of ``None`` means use the default background color; otherwise a toolkit-compatible color should be returned. Note that all columns for the specified table row will use the background color value returned. :py:meth:`~TabularAdapter.get_image` Returns the image to display for a specified cell. A result of ``None`` means no image will be displayed in the specified table cell. Otherwise the result should either be the name of the image, or an :py:class:`~pyface.image_resource.ImageResource` object specifying the image to display. A name is allowed in the case where the image is specified in the :py:class:`~traitsui.editors.tabular_editor.TabularEditor` :py:attr:`~traitsui.editors.tabular_editor.TabularEditor.images` trait. In that case, the name should be the same as the string specified in the :py:class:`~pyface.image_resource.ImageResource` constructor. :py:meth:`~TabularAdapter.get_format` Returns the Python formatting string to apply to the specified cell. The resulting of formatting with this string will be used as the text to display it in the table. The return can be any Python string containing exactly one old-style Python formatting sequence, such as ``'%.4f'`` or ``'(%5.2f)'``. :py:meth:`~TabularAdapter.get_text` Returns a string containing the text to display for a specified cell. If the underlying data representation for a specified item is not a string, then it is your responsibility to convert it to one before returning it as the result. :py:meth:`~TabularAdapter.set_text` Sets the value for the specified cell. This method is called when the user completes an editing operation on a table cell. The string specified by ``text`` is the value that the user has entered in the table cell. If the underlying data does not store the value as text, it is your responsibility to convert ``text`` to the correct representation used. :py:meth:`~TabularAdapter.get_tooltip` Returns a string containing the tooltip to display for a specified cell. You should return the empty string if you do not wish to display a tooltip. The following are the remaining adapter methods, which are not sensitive to the type of item or column data: :py:meth:`~TabularAdapter.get_item` Returns the specified row item. The value returned should be the value that exists (or *logically* exists) at the specified ``row`` in your data. If your data is not really a list or array, then you can just use ``row`` as an integer *key* or *token* that can be used to retrieve a corresponding item. The value of ``row`` will always be in the range: 0 <= row < ``len(object, trait)`` (i.e. the result returned by the adapter :py:meth:`len` method). :py:meth:`~TabularAdapter.len` Returns the number of row items in the specified ``object.trait``. The result should be an integer greater than or equal to 0. :py:meth:`~TabularAdapter.delete` Deletes the specified row item. This method is only called if the *delete* operation is specified in the :py:class:`~traitsui.editors.tabular_editor.TabularEditor` :py:attr:`~traitsui.editors.tabular_editor.TabularEditor.operation` trait, and the user requests that the item be deleted from the table. The adapter can still choose not to delete the specified item if desired, although that may prove confusing to the user. :py:meth:`~TabularAdapter.insert` Inserts ``value`` at the specified ``object.trait[row]`` index. The specified ``value`` can be: - An item being moved from one location in the data to another. - A new item created by a previous call to :py:meth:`~TabularAdapter.get_default_value`. - An item the adapter previously approved via a call to :py:meth:`~TabularAdapter.get_can_drop`. The adapter can still choose not to insert the item into the data, although that may prove confusing to the user. :py:meth:`~TabularAdapter.get_default_value` Returns a new default value for the specified ``object.trait`` list. This method is called when *insert* or *append* operations are allowed and the user requests that a new item be added to the table. The result should be a new instance of whatever underlying representation is being used for table items. Creating a Custom TabularAdapter -------------------------------- Having just taken a look at the core :py:class:`TabularAdapter` interface, you might now be thinking that there are an awful lot of methods that need to be specified to get an adapter up and running. But as we mentioned earlier :py:class:`TabularAdapter` is not an abstract base class. It is a concrete base class with implementations for each of the methods in its interface. And the implementations are written in such a way that you will hopefully hardly ever need to override them. In this section, we'll explain the general implementation style used by these methods, and how you can take advantage of them in creating your own adapters. One of the things you probably noticed as you read through the core adapter interface section is that most of the methods have names of the form: ``get_xxx`` or ``set_xxx``, which is similar to the familiar *getter/setter* pattern used when defining trait properties. The adapter interface is purposely defined this way so that it can expose and leverage a simple set of design rules. The design rules are followed consistently in the implementations of all of the adapter methods described in the first section of the core adapter interface, so that once you understand how they work, you can easily apply the design pattern to all items in that section. Then, only in the case where the design rules will not work for your application will you ever have to override any of those :py:class:`TabularAdapter` base class method implementations. So the first thing to understand is that if an adapter method name has the form: ``get_xxx`` or ``set_xxx`` it really is dealing with some kind of trait called ``xxx``, or which contains ``xxx`` in its name. For example, the :py:meth`~TabularAdapter.get_alignment` method retrieves the value of some :py:attr:`~TabularAdapter.alignment` trait defined on the adapter. In the following discussion we'll simply refer to an attribute name generically as *attribute*, but you will need to replace it by an actual attribute name (e.g. :py:attr:`~TabularAdapter.alignment`) in your adapter. The next thing to keep in mind is that the adapter interface is designed to easily deal with items that are not all of the same type. As we just said, the design rules apply to all adapter methods in the first group, which were defined as methods which are sensitive to an item's type. Item type sensitivity plays an important part in the design rules, as we will see shortly. With this in mind, we now describe the simple design rules used by the first group of methods in the :py:class:`TabularAdapter` class: - When getting or setting an adapter attribute, the method first retrieves the underlying item for the specified data row. The item, and type (i.e. class) of the item, are then used in the next rule. - The method gets or sets the first trait it finds on the adapter that matches one of the following names: - *classname_columnid_attribute* - *classsname_attribute* - *columnid_attribute* - *attribute* where: - *classname* is the name of the class of the item found in the first step, or one of its base class names, searched in the order defined by the *mro* (**method resolution order**) for the item's class. - *columnid* is the column id specified by the developer in the adapter's *column* trait for the specified table column. - *attribute* is the attribute name as described previously (e.g. *alignment*). Note that this last rule always finds a matching trait, since the :py:class:`TabularAdapter` base class provides traits that match the simple *attribute* form for all attributes these rules apply to. Some of these are simple traits, while others are properties. We'll describe the behavior of all these *default* traits shortly. The basic idea is that rather than override the first group of core adapter methods, you simply define one or more simple traits or trait properties on your :py:class:`TabularAdapter` subclass that provide or accept the specified information. All of the adapter methods in the first group provide a number of arguments, such as ``object``, ``trait``, ``row`` and ``column``. In order to define a trait property, which cannot be passed this information directly, the adapter always stores the arguments and values it computes in the following adapter traits, where they can be easily accessed by a trait getter or setter method: - ``row``: The table row being accessed. - ``column``: The column id of the table column being accessed (not its index). - ``item``: The data item for the specified table row (i.e. the item determined in the first step described above). - ``value``: In the case of a *set_xxx* method, the value to be set; otherwise it is ``None``. As mentioned previously, the :py:class:`TabularAdapter` class provides trait definitions for all of the attributes these rules apply to. You can either use the default values as they are, override the default, set a new value, or completely replace the trait definition in a subclass. A description of the default trait implementation for each attribute is as follows: :py:attr:`~TabularAdapter.default_value` = ``Any('')`` The default value for a new row. The default value is the empty string, but you will normally need to assign a different (default) value. :py:attr:`~TabularAdapter.format` = ``Str('%s')`` The default Python formatting string for a column item. The default value is ``'%s'`` which will simply convert the column item to a displayable string value. :py:attr:`~TabularAdapter.text` = ``Property`` The text to display for the column item. The implementation of the property checks the type of the column's *column id*: - If it is an integer, it returns ``format % item[column_id]``. - Otherwise, it returns ``format % item.column_id``. Note that ``format`` refers to the value returned by a call to :py:meth:`~TabularAdapter.get_format` for the current column item. :py:attr:`~TabularAdapter.text_color` = ``Property`` The text color for a row item. The property implementation checks to see if the current table row is even or odd, and based on the result returns the value of the :py:attr:`~TabularAdapter.even_text_color` or :py:attr:`~TabularAdapter.odd_text_color` trait if the value is not ``None``, and the value of the :py:attr:`~TabularAdapter.default_text_color` trait if it is. The definition of these additional traits are as follows: - :py:attr:`~TabularAdapter.odd_text_color` = ``Color(None)`` - :py:attr:`~TabularAdapter.even_text_color` = ``Color(None)`` - :py:attr:`~TabularAdapter.default_text_color` = ``Color(None)`` Remember that a ``None`` value means use the default text color. :py:attr:`~TabularAdapter.bg_color` = ``Property`` The background color for a row item. The property implementation checks to see if the current table row is even or odd, and based on the result returns the value of the :py:attr:`~TabularAdapter.even_bg_color` or :py:attr:`~TabularAdapter.odd_bg_color` trait if the value is not ``None``, and the value of the :py:attr:`~TabularAdapter.default_bg_color` trait if it is. The definition of these additional traits are as follows: - :py:attr:`~TabularAdapter.odd_bg_color` = ``Color(None)`` - :py:attr:`~TabularAdapter.even_bg_color` = ``Color(None)`` - :py:attr:`~TabularAdapter.default_bg_color` = ``Color(None)`` Remember that a ``None`` value means use the default background color. :py:attr:`~TabularAdapter.alignment` = ``Enum('left', 'center', 'right')`` The alignment to use for a specified column. The default value is ``'left'``. :py:attr:`~TabularAdapter.width` = ``Float(-1)`` The width of a specified column. The default value is -1, which means a dynamically sized column with an *unnormalized fractional* value of 0.1. :py:attr:`~TabularAdapter.can_edit` = ``Bool(True)`` Specifies whether the text value of the current item can be edited. The default value is ``True``, which means that the user can edit the value. :py:attr:`~TabularAdapter.drag` = ``Property`` A property which returns the value to be dragged for a specified row item. The property implementation simply returns the current row item. :py:attr:`~TabularAdapter.can_drop` = ``Bool(False)`` Specifies whether the specified value be dropped on the current item. The default value is ``False``, meaning that the value cannot be dropped. :py:attr:`~TabularAdapter.dropped` = ``Enum('after', 'before')`` Specifies where a dropped item should be placed in the table relative to the item it is dropped on. The default value is ``'after'``. :py:attr:`~TabularAdapter.font` = ``Font`` The font to use for the current item. The default value is the standard default Traits font value. :py:attr:`~TabularAdapter.image` = ``Str(None)`` The name of the default image to use for a column. The default value is ``None``, which means that no image will be displayed for the column. :py:attr:`~TabularAdapter.tooltip` = ``Str`` The tooltip information for a column item. The default value is the empty string, which means no tooltip information will be displayed for the column. The preceding discussion applies to all of the methods defined in the first group of :py:attr:`TabularAdapter` interface methods. However, the design rules do not apply to the remaining five adapter methods, although they all provide a useful default implementation: :py:meth:`~TabularAdapter.get_item` The default implementation assumes the trait defined by ``object.trait`` is a *sequence* and attempts to return the value at index ``row``. If an error occurs, it returns ``None`` instead. This definition should work correctly for lists, tuples and arrays, or any other object that is indexable, but will have to be overridden for all other cases. Note that this method is the one called in the first design rule described previously to retrieve the item at the current table row. :py:meth:`~TabularAdapter.len` Again, the default implementation assumes the trait defined by ``object.trait`` is a *sequence* and attempts to return the result of calling ``len(object.trait)``. It will need to be overridden for any type of data which for which :py:func:`len` will not work. :py:meth:`~TabularAdapter.delete` The default implementation assumes the trait defined by ``object.trait`` is a mutable sequence and attempts to perform a ``del object.trait[row]`` operation. :py:meth:`~TabularAdapter.insert` The default implementation assumes the trait defined by ``object.trait`` is a mutable sequence and attempts to perform an ``object.trait[row:row] = [value]`` operation. :py:meth:`~TabularAdapter.get_default_value` The default implementation simply returns the value of the adapter's :py:attr:`~TabularAdapter.default_value` trait. Examples -------- There are a number of examples of use of the :py:class:`TabularAdapter` in the TraitsUI demos: - :github-demo:`TabularEditor ` - :github-demo:`TabularEditor (auto-update) ` - :github-demo:`NumPy array TabularEditor ` The ListStrAdapter Class ======================== .. module:: traitsui.list_str_adapter :noindex: Although the :py:class:`~traitsui.editors.list_str_editor.ListStrEditor` editor is frequently used, as might be expected, with lists of strings, it also provides facilities to edit lists of other object types that can be adapted to produce strings for display and editing via :py:class:`ListStrAdapter` subclasses The design of the :py:class:`ListStrAdapter` base class follows the same design as the :py:class:`~traitsui.tabular_adapter.TabularAdapter`, simplified by the fact that there are only rows, no columns. However, the names and intents of the various methods and traits are the same as the :py:class:`~traitsui.tabular_adapter.TabularAdapter`, and so the approaches discussed in the previous section work for the :py:class:`ListStrAdapter` as well.