File: qt_tree_view.py

package info (click to toggle)
python-enamlx 0.6.4-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 388 kB
  • sloc: python: 3,338; makefile: 18
file content (240 lines) | stat: -rw-r--r-- 8,136 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# -*- coding: utf-8 -*-
"""
Copyright (c) 2015, Jairus Martin.
Distributed under the terms of the MIT License.
The full license is in the file COPYING.txt, distributed with this software.
Created on Aug 28, 2015
"""
from atom.api import Instance, Int, Typed
from enaml.application import timed_call
from enaml.core.pattern import Pattern
from enaml.qt.qt_widget import QtWidget
from qtpy.QtCore import QAbstractItemModel, QModelIndex
from qtpy.QtWidgets import QTreeView

from enamlx.qt.qt_abstract_item import RESIZE_MODES, AbstractQtWidgetItem
from enamlx.qt.qt_abstract_item_view import QAbstractAtomItemModel, QtAbstractItemView
from enamlx.widgets.tree_view import (
    ProxyTreeView,
    ProxyTreeViewColumn,
    ProxyTreeViewItem,
)


class QAtomTreeModel(QAbstractAtomItemModel, QAbstractItemModel):
    def rowCount(self, parent):
        d = self.declaration
        if d.vertical_headers:
            return len(d.vertical_headers)
        elif parent.isValid():
            item = parent.internalPointer()
            d = item.declaration
        return len(d.items) if d and not d.is_destroyed else 0

    def columnCount(self, parent):
        d = self.declaration
        if d.horizontal_headers:
            return len(d.horizontal_headers)
        elif parent.isValid():
            item = parent.internalPointer()
            d = item.declaration
        return len(d._columns) if d and not d.is_destroyed else 0

    def index(self, row, column, parent):
        """The index should point to the corresponding QtControl in the
        enaml object hierarchy.
        """
        item = parent.internalPointer()
        #: If the parent is None
        d = self.declaration if item is None else item.declaration
        if row < len(d._items):
            proxy = d._items[row].proxy
            assert isinstance(proxy, QtTreeViewItem), "Invalid item {}".format(proxy)
        else:
            proxy = d.proxy
        return self.createIndex(row, column, proxy)

    def parent(self, index):
        if not index.isValid():
            return QModelIndex()
        item = index.internalPointer()
        if not isinstance(item, QtTreeViewItem) or item.is_destroyed:
            return QModelIndex()
        parent = item.parent()
        if not isinstance(parent, QtTreeViewItem) or parent.is_destroyed:
            return QModelIndex()
        d = parent.declaration
        return self.createIndex(d.row, 0, parent)

    def itemAt(self, index=None):
        if not index or not index.isValid():
            return
        item = index.internalPointer()
        assert isinstance(
            item, QtTreeViewItem
        ), "Invalid index: {} at ({},{}) {}".format(
            index, index.row(), index.column(), item
        )
        d = item.declaration
        try:
            c = index.column()  # - d.visible_column
            return d._columns[c].proxy
        except IndexError:
            return


class QtTreeView(QtAbstractItemView, ProxyTreeView):
    #: Tree widget
    widget = Typed(QTreeView)

    #: Root index
    index = Instance(QModelIndex, ())

    def create_widget(self):
        self.widget = QTreeView(self.parent_widget())

    def init_widget(self):
        super(QtTreeView, self).init_widget()
        d = self.declaration
        self.set_show_root(d.show_root)

    def init_model(self):
        self.set_model(QAtomTreeModel(parent=self.widget))

    # -------------------------------------------------------------------------
    # Widget Setters
    # -------------------------------------------------------------------------
    def set_show_root(self, show):
        self.widget.setRootIsDecorated(show)

    def set_cell_padding(self, padding):
        self.widget.setStyleSheet("QTreeView::item { padding: %ipx }" % padding)

    def set_horizontal_minimum_section_size(self, size):
        self.widget.header().setMinimumSectionSize(size)

    def set_horizontal_stretch(self, stretch):
        self.widget.header().setStretchLastSection(stretch)

    def set_horizontal_headers(self, headers):
        self.widget.header().model().layoutChanged.emit()

    def set_resize_mode(self, mode):
        self.widget.header().setSectionResizeMode(RESIZE_MODES[mode])

    def set_show_horizontal_header(self, show):
        header = self.widget.header()
        header.show() if show else header.hide()

    # -------------------------------------------------------------------------
    # View refresh handlers
    # -------------------------------------------------------------------------
    def _refresh_visible_column(self, value):
        self._pending_column_refreshes -= 1
        if self._pending_column_refreshes == 0:
            d = self.declaration
            # TODO: What about parents???
            try:
                cols = self.model.columnCount(self.index) - d.visible_columns
                d.visible_column = max(0, min(value, cols))
            except RuntimeError:
                #: Since refreshing is deferred several ms later
                pass

    def _refresh_visible_row(self, value):
        self._pending_row_refreshes -= 1
        if self._pending_row_refreshes == 0:
            d = self.declaration
            try:
                rows = self.model.rowCount(self.index) - d.visible_rows
                d.visible_row = max(0, min(value, rows))
            except RuntimeError:
                pass


class AbstractQtTreeViewItem(AbstractQtWidgetItem):
    """Base TreeViewItem class"""

    #: Pending refreshes when loading widgets
    _refresh_count = Int(0)

    #: Time to wait before loading widget
    _loading_interval = Int(100)

    def create_widget(self):
        if self.declaration:
            for child in self.children():
                if isinstance(child, (Pattern, QtWidget)):
                    self.delegate = child

    def set_row(self, row):
        self._update_index()

    def set_column(self, column):
        self._update_index()

    def _default_index(self):
        d = self.declaration
        return self.view.model.index(d.row, d.column, self.parent().index)

    def _update_index(self):
        self.index = self._default_index()
        if self.delegate:
            self._refresh_count += 1
            timed_call(self._loading_interval, self._update_delegate)

    def _update_delegate(self):
        """Update the delegate cell widget. This is deferred so it
        does not get called until the user is done scrolling.
        """
        self._refresh_count -= 1
        if self._refresh_count != 0:
            return

        try:
            delegate = self.delegate
            if not self._is_visible():
                return

            # The table destroys when it goes out of view
            # so we always have to make a new one
            delegate.create_widget()
            delegate.init_widget()

            #  Set the index widget
            self.view.widget.setIndexWidget(self.index, delegate.widget)
        except RuntimeError:
            # Since this is deferred, the table could be deleted already
            # and a RuntimeError is possible
            pass

    def _is_visible(self):
        return self.index.isValid()

    def data_changed(self, change):
        """Notify the model that data has changed in this cell!"""
        self.view.model.dataChanged.emit(self.index, self.index)


class QtTreeViewItem(AbstractQtTreeViewItem, ProxyTreeViewItem):
    def _default_view(self):
        """If this is the root item, return the parent
        which must be a TreeView, otherwise return the
        parent Item's view.
        """
        parent = self.parent()
        if isinstance(parent, QtTreeView):
            return parent
        return parent.view


class QtTreeViewColumn(AbstractQtTreeViewItem, ProxyTreeViewColumn):
    def _default_view(self):
        """Since the TreeViewColumn must be a child of a TreeViewItem,
        simply return the Item's view.
        """
        return self.parent().view

    def _default_index(self):
        d = self.declaration
        return self.view.model.index(d.row, d.column, self.parent().index)