1
#------------------------------------------------------------------------------
2
# Copyright (c) 2008, Riverbank Computing Limited
5
# This software is provided without warranty under the terms of the BSD license.
6
# However, when used with the GPL version of PyQt the additional terms described
7
# in the PyQt GPL exception also apply.
9
# Author: Riverbank Computing Limited
10
#------------------------------------------------------------------------------
12
""" Defines the table model used by the table editor.
15
#-------------------------------------------------------------------------------
17
#-------------------------------------------------------------------------------
19
from pyface.qt import QtCore, QtGui
21
from traitsui.ui_traits import SequenceTypes
23
#-------------------------------------------------------------------------------
25
#-------------------------------------------------------------------------------
27
# Mapping for trait alignment values to qt4 horizontal alignment constants
29
'left': QtCore.Qt.AlignLeft,
30
'center': QtCore.Qt.AlignHCenter,
31
'right': QtCore.Qt.AlignRight,
34
# Mapping for trait alignment values to qt4 vertical alignment constants
36
'top': QtCore.Qt.AlignTop,
37
'center': QtCore.Qt.AlignVCenter,
38
'bottom': QtCore.Qt.AlignBottom,
41
# MIME type for internal table drag/drop operations
42
mime_type = 'traits-ui-table-editor'
44
#-------------------------------------------------------------------------------
46
#-------------------------------------------------------------------------------
48
class TableModel(QtCore.QAbstractTableModel):
49
"""The model for table data."""
51
def __init__(self, editor, parent=None):
52
"""Initialise the object."""
54
QtCore.QAbstractTableModel.__init__(self, parent)
58
#---------------------------------------------------------------------------
59
# QAbstractTableModel interface:
60
#---------------------------------------------------------------------------
62
def rowCount(self, mi):
63
"""Reimplemented to return the number of rows."""
65
return len(self._editor.items())
67
def columnCount(self, mi):
68
"""Reimplemented to return the number of columns."""
70
return len(self._editor.columns)
72
def data(self, mi, role):
73
"""Reimplemented to return the data."""
75
obj = self._editor.items()[mi.row()]
76
column = self._editor.columns[mi.column()]
78
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
79
text = column.get_value(obj)
83
elif role == QtCore.Qt.ToolTipRole:
84
tooltip = column.get_tooltip(obj)
88
elif role == QtCore.Qt.FontRole:
89
font = column.get_text_font(obj)
91
return QtGui.QFont(font)
93
elif role == QtCore.Qt.TextAlignmentRole:
94
string = column.get_horizontal_alignment(obj)
95
h_alignment = h_alignment_map.get(string, QtCore.Qt.AlignLeft)
96
string = column.get_vertical_alignment(obj)
97
v_alignment = v_alignment_map.get(string, QtCore.Qt.AlignVCenter)
98
return (h_alignment | v_alignment)
100
elif role == QtCore.Qt.BackgroundRole:
101
color = column.get_cell_color(obj)
102
if color is not None:
103
if isinstance(color, SequenceTypes):
104
q_color = QtGui.QColor(*color)
106
q_color = QtGui.QColor(color)
107
return QtGui.QBrush(q_color)
109
elif role == QtCore.Qt.ForegroundRole:
110
color = column.get_text_color(obj)
111
if color is not None:
112
if isinstance(color, SequenceTypes):
113
q_color = QtGui.QColor(*color)
115
q_color = QtGui.QColor(color)
116
return QtGui.QBrush(q_color)
118
elif role == QtCore.Qt.UserRole:
124
"""Reimplemented to set editable and movable status."""
126
flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
131
editor = self._editor
132
obj = editor.items()[mi.row()]
133
column = editor.columns[mi.column()]
135
if editor.factory.editable and column.is_editable(obj):
136
flags |= QtCore.Qt.ItemIsEditable
138
if editor.factory.reorderable:
139
flags |= QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
143
def headerData(self, section, orientation, role):
144
"""Reimplemented to return the header data."""
146
if orientation == QtCore.Qt.Horizontal:
148
editor = self._editor
149
column = editor.columns[section]
151
if role == QtCore.Qt.DisplayRole:
152
return column.get_label()
154
elif orientation == QtCore.Qt.Vertical:
156
if role == QtCore.Qt.DisplayRole:
157
return str(section + 1)
161
def insertRow(self, row, parent=QtCore.QModelIndex(), obj=None):
162
"""Reimplemented to allow creation of new rows. Added an optional
163
arg to allow the insertion of an existing row object."""
165
editor = self._editor
167
obj = editor.create_new_row()
169
self.beginInsertRows(parent, row, row)
170
editor.callx(editor.items().insert, row, obj)
174
def insertRows(self, row, count, parent=QtCore.QModelIndex()):
175
"""Reimplemented to allow creation of new rows."""
177
editor = self._editor
178
items = editor.items()
179
self.beginInsertRows(parent, row, row + count - 1)
180
for i in xrange(count):
181
editor.callx(items.insert, row + i, editor.create_new_row())
185
def removeRows(self, row, count, parent=QtCore.QModelIndex()):
186
"""Reimplemented to allow row deletion, as well as reordering via drag
189
editor = self._editor
190
items = editor.items()
191
self.beginRemoveRows(parent, row, row + count - 1)
192
for i in xrange(count):
193
editor.callx(items.pop, row + i)
198
"""Reimplemented to expose our internal MIME type for drag and drop
201
types = QtCore.QStringList()
202
types.append(mime_type)
205
def mimeData(self, indexes):
206
"""Reimplemented to generate MIME data containing the rows of the
207
current selection."""
209
mime_data = QtCore.QMimeData()
210
rows = list(set([ index.row() for index in indexes ]))
211
data = QtCore.QByteArray(str(rows[0]))
213
data.append(' %i' % row)
214
mime_data.setData(mime_type, data)
217
def dropMimeData(self, mime_data, action, row, column, parent):
218
"""Reimplemented to allow items to be moved."""
220
if action == QtCore.Qt.IgnoreAction:
223
data = mime_data.data(mime_type)
227
current_rows = map(int, str(data).split(' '))
228
self.moveRows(current_rows, parent.row())
231
def supportedDropActions(self):
232
"""Reimplemented to allow items to be moved."""
234
return QtCore.Qt.MoveAction
236
#---------------------------------------------------------------------------
237
# TableModel interface:
238
#---------------------------------------------------------------------------
240
def moveRow(self, old_row, new_row):
241
"""Convenience method to move a single row."""
243
return self.moveRows([old_row], new_row)
245
def moveRows(self, current_rows, new_row):
246
"""Moves a sequence of rows (provided as a list of row indexes) to a new
249
# Sort rows in descending order so they can be removed without
250
# invalidating the indices.
252
current_rows.reverse()
254
# If the the highest selected row is lower than the destination, do an
255
# insertion before rather than after the destination.
256
if current_rows[-1] < new_row:
259
# Remove selected rows...
260
items = self._editor.items()
262
for row in current_rows:
265
objects.insert(0, items[row])
268
# ...and add them at the new location.
269
for i, obj in enumerate(objects):
270
self.insertRow(new_row + i, obj=obj)
272
# Update the selection for the new location.
273
self._editor.set_selection(objects)
275
#-------------------------------------------------------------------------------
276
# 'SortFilterTableModel' class:
277
#-------------------------------------------------------------------------------
279
class SortFilterTableModel(QtGui.QSortFilterProxyModel):
280
"""A wrapper for the TableModel which provides sorting and filtering
283
def __init__(self, editor, parent=None):
284
"""Initialise the object."""
286
QtGui.QSortFilterProxyModel.__init__(self, parent)
288
self._editor = editor
290
#---------------------------------------------------------------------------
291
# QSortFilterProxyModel interface:
292
#---------------------------------------------------------------------------
294
def filterAcceptsRow(self, source_row, source_parent):
295
""""Reimplemented to use a TableFilter for filtering rows."""
297
if self._editor._filtered_cache is None:
300
return self._editor._filtered_cache[source_row]
302
def filterAcceptsColumn(self, source_column, source_parent):
303
"""Reimplemented to save time, because we always return True."""
307
def lessThan(self, left_mi, right_mi):
308
"""Reimplemented to sort according to the 'cmp' method defined for
311
editor = self._editor
312
column = editor.columns[left_mi.column()]
313
items = editor.items()
314
left, right = items[left_mi.row()], items[right_mi.row()]
316
return column.cmp(left, right) < 0
318
#---------------------------------------------------------------------------
319
# SortFilterTableModel interface:
320
#---------------------------------------------------------------------------
322
def moveRow(self, old_row, new_row):
323
"""Convenience method to move a single row."""
325
return self.moveRows([old_row], new_row)
327
def moveRows(self, current_rows, new_row):
328
"""Delegate to source model with mapped rows."""
330
source = self.sourceModel()
331
current_rows = [ self.mapToSource(self.index(row, 0)).row()
332
for row in current_rows ]
333
new_row = self.mapToSource(self.index(new_row, 0)).row()
334
source.moveRows(current_rows, new_row)