1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
29
"""tablemodel.py -- Model classes for TableView. """
33
from AppKit import NSDragOperationNone, NSTableViewDropOn, protocols
34
from Foundation import NSObject, NSNotFound
35
from objc import YES, NO, nil
37
from miro import fasttypes
38
from miro import signals
39
from miro.plat.frontends.widgets import wrappermap
40
from miro.plat.frontends.widgets.simple import Image
42
def list_from_nsindexset(index_set):
44
index = index_set.firstIndex()
45
while (index != NSNotFound):
47
index = index_set.indexGreaterThanIndex_(index)
50
class RowList(object):
51
"""RowList is a Linked list that has some optimizations for looking up
55
self.list = fasttypes.LinkedList()
59
return self.list.firstIter()
62
return self.list.lastIter()
64
def insertBefore(self, iter, value):
67
return self.list.append(value)
69
return self.list.insertBefore(iter, value)
71
def append(self, value):
72
return self.list.append(value)
77
def __getitem__(self, iter):
78
return self.list[iter]
81
iter = self.firstIter()
82
while iter != self.lastIter():
86
def remove(self, iter):
88
return self.list.remove(iter)
90
def nth_iter(self, index):
93
elif index >= len(self):
95
if len(self.iter_cache) == 0:
96
self.iter_cache.append(self.firstIter())
98
return self.iter_cache[index].copy()
101
iter = self.iter_cache[-1].copy()
102
index -= len(self.iter_cache) - 1
103
for x in xrange(index):
105
self.iter_cache.append(iter.copy())
108
class TableModelBase(signals.SignalEmitter):
109
"""Base class for TableModel and TreeTableModel."""
110
def __init__(self, *column_types):
111
signals.SignalEmitter.__init__(self)
112
self.row_list = RowList()
113
self.column_types = column_types
114
self.create_signal('row-changed')
115
self.create_signal('row-added')
116
self.create_signal('row-will-be-removed')
118
def check_column_values(self, column_values):
119
if len(self.column_types) != len(column_values):
120
raise ValueError("Wrong number of columns")
121
# We might want to do more typechecking here
123
def update_value(self, iter, index, value):
124
old_row = list(iter.value().values)
125
iter.value().values[index] = value
126
self.emit('row-changed', iter, old_row)
128
def update(self, iter, *column_values):
129
old_row = list(iter.value().values)
130
iter.value().update_values(column_values)
131
self.emit('row-changed', iter, old_row)
133
def remove(self, iter):
134
row_list = self.containing_list(iter)
135
self.emit('row-will-be-removed', iter)
136
rv = row_list.remove(iter)
137
if rv == row_list.lastIter():
141
def nth_iter(self, index):
142
return self.row_list.nth_iter(index)
144
def next_iter(self, iter):
145
row_list = self.containing_list(iter)
148
if retval == row_list.lastIter():
153
def first_iter(self):
154
if len(self.row_list) > 0:
155
return self.row_list.firstIter()
160
return len(self.row_list)
162
def __getitem__(self, iter):
166
return iter(self.row_list)
168
class TableRow(object):
169
"""See https://develop.participatoryculture.org/index.php/WidgetAPITableView for a description of the API for this class."""
170
def __init__(self, column_values):
171
self.update_values(column_values)
173
def update_values(self, column_values):
174
self.values = list(column_values)
176
def __getitem__(self, index):
177
return self.values[index]
180
return len(self.values)
183
return iter(self.values)
185
class TableModel(TableModelBase):
186
"""See https://develop.participatoryculture.org/index.php/WidgetAPITableView for a description of the API for this class."""
187
def __init__(self, *column_types):
188
TableModelBase.__init__(self, column_types)
189
self.row_indexes = {}
191
def remember_row_at_index(self, row, index):
192
if row not in self.row_indexes:
193
self.row_indexes[row] = index
195
def get_index_of_row(self, row):
197
return self.row_indexes[row]
199
iter = self.row_list.firstIter()
201
while iter != self.row_list.lastIter():
202
current_row = iter.value()
203
self.row_indexes[current_row] = index
204
if current_row is row:
208
raise LookupError("%s is not in this table" % row)
210
def containing_list(self, iter):
213
def append(self, *column_values):
214
self.row_indexes = {}
215
retval = self.row_list.append(TableRow(column_values))
216
self.emit('row-added', retval)
219
def remove(self, iter):
220
self.row_indexes = {}
221
return TableModelBase.remove(self, iter)
223
def insert_before(self, iter, *column_values):
224
self.row_indexes = {}
225
row = TableRow(column_values)
226
retval = self.row_list.insertBefore(iter, row)
227
self.emit('row-added', retval)
230
def iter_for_row(self, tableview, row):
231
return self.row_list.nth_iter(row)
233
class TreeNode(NSObject, TableRow):
234
"""A row in a TreeTableModel"""
236
# Implementation note: these need to be NSObjects because we return them
237
# to the NSOutlineView.
239
def initWithValues_parent_(self, column_values, parent):
240
self.children = RowList()
241
self.update_values(column_values)
246
def create_(values, parent):
247
return TreeNode.alloc().initWithValues_parent_(values, parent)
249
def iterchildren(self):
250
return iter(self.children)
252
class TreeTableModel(TableModelBase):
253
"""https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
254
def __init__(self, *column_values):
255
TableModelBase.__init__(self, *column_values)
256
self.iter_for_item = {}
258
def containing_list(self, iter):
259
return self.row_list_for_iter(iter.value().parent)
261
def row_list_for_iter(self, iter):
265
return iter.value().children
267
def remember_iter(self, iter):
268
self.iter_for_item[iter.value()] = iter
271
def append(self, *column_values):
272
retval = self.row_list.append(TreeNode.create_(column_values, None))
273
self.emit('row-added', retval)
274
return self.remember_iter(retval)
276
def forget_iter_for_item(self, item):
277
del self.iter_for_item[item]
278
for child in item.children:
279
self.forget_iter_for_item(child)
281
def remove(self, iter):
282
self.forget_iter_for_item(iter.value())
283
return TableModelBase.remove(self, iter)
285
def insert_before(self, iter, *column_values):
286
row = TreeNode.create_(column_values, self.parent_iter(iter))
287
retval = self.containing_list(iter).insertBefore(iter, row)
288
self.emit('row-added', retval)
289
return self.remember_iter(retval)
291
def append_child(self, iter, *column_values):
292
row_list = self.row_list_for_iter(iter)
293
retval = row_list.append(TreeNode.create_(column_values, iter))
294
self.emit('row-added', retval)
295
return self.remember_iter(retval)
297
def child_iter(self, iter):
298
row_list = iter.value().children
299
if len(row_list) == 0:
302
return row_list.firstIter()
304
def nth_child_iter(self, iter, index):
305
row_list = self.row_list_for_iter(iter)
306
return row_list.nth_iter(index)
308
def has_child(self, iter):
309
return len(iter.value().children) > 0
311
def children_count(self, iter):
313
return len(iter.value().children)
315
return len(self.row_list)
317
def children_iters(self, iter):
318
return self.iters_in_rowlist(self.row_list_for_iter(iter))
320
def parent_iter(self, iter):
321
return iter.value().parent
323
def iter_for_row(self, tableview, row):
324
return self.iter_for_item[tableview.itemAtRow_(row)]
326
def get_column_data(row, column):
327
attr_map = column.identifier()
328
return dict((name, row[index]) for name, index in attr_map.items())
330
class DataSourceBase(NSObject):
331
def initWithModel_(self, model):
333
self.drag_source = None
334
self.drag_dest = None
336
def setDragSource_(self, drag_source):
337
self.drag_source = drag_source
339
def setDragDest_(self, drag_dest):
340
self.drag_dest = drag_dest
342
def view_writeColumnData_ToPasteboard_(self, view, data, pasteboard):
343
if not self.drag_source:
345
wrapper = wrappermap.wrapper(view)
346
drag_data = self.drag_source.begin_drag(wrapper, data)
349
pasteboard.declareTypes_owner_(drag_data.keys(), self)
350
for type, value in drag_data.items():
351
pasteboard.setString_forType_(value, type)
354
def calcType_(self, drag_info):
355
source_actions = drag_info.draggingSourceOperationMask()
356
if not (self.drag_dest and
357
(self.drag_dest.allowed_actions() | source_actions)):
359
types = self.drag_dest.allowed_types()
360
return drag_info.draggingPasteboard().availableTypeFromArray_(types)
362
def validateDrop_dragInfo_parentIter_position_(self, view, drag_info,
364
type = self.calcType_(drag_info)
366
wrapper = wrappermap.wrapper(view)
367
return self.drag_dest.validate_drop(wrapper, self.model, type,
368
drag_info.draggingSourceOperationMask(), parent,
371
return NSDragOperationNone
373
def acceptDrop_dragInfo_parentIter_position_(self, view, drag_info,
375
type = self.calcType_(drag_info)
377
data = drag_info.draggingPasteboard().stringForType_(type)
378
wrapper = wrappermap.wrapper(view)
379
return self.drag_dest.accept_drop(wrapper, self.model, type,
380
drag_info.draggingSourceOperationMask(), parent,
385
class MiroTableViewDataSource(DataSourceBase, protocols.NSTableDataSource):
386
def numberOfRowsInTableView_(self, table_view):
387
return len(self.model)
389
def tableView_objectValueForTableColumn_row_(self, table_view, column, row):
390
node = self.model.nth_iter(row).value()
391
self.model.remember_row_at_index(node, row)
392
return get_column_data(node.values, column)
394
def tableView_writeRowsWithIndexes_toPasteboard_(self, tableview, rowIndexes,
396
indexes = list_from_nsindexset(rowIndexes)
397
data = [self.model[self.model.nth_iter(i)] for i in indexes]
398
return self.view_writeColumnData_ToPasteboard_(tableview, data,
401
def translateRow_operation_(self, row, operation):
402
if operation == NSTableViewDropOn:
403
return (self.model.nth_iter(row),), -1
407
def tableView_validateDrop_proposedRow_proposedDropOperation_(self,
408
tableview, drag_info, row, operation):
409
parent, position = self.translateRow_operation_(row, operation)
410
return self.validateDrop_dragInfo_parentIter_position_(tableview,
411
drag_info, parent, position)
413
def tableView_acceptDrop_row_dropOperation_(self,
414
tableview, drag_info, row, operation):
415
parent, position = self.translateRow_operation_(row, operation)
416
return self.acceptDrop_dragInfo_parentIter_position_(tableview,
417
drag_info, parent, position)
419
class MiroOutlineViewDataSource(DataSourceBase, protocols.NSOutlineViewDataSource):
420
def outlineView_child_ofItem_(self, view, child, item):
422
row_list = self.model.row_list
424
row_list = item.children
425
return row_list.nth_iter(child).value()
427
def outlineView_isItemExpandable_(self, view, item):
428
if item is not nil and hasattr(item, 'children'):
429
return len(item.children) > 0
431
return len(self.model) > 0
433
def outlineView_numberOfChildrenOfItem_(self, view, item):
434
if item is not nil and hasattr(item, 'children'):
435
return len(item.children)
437
return len(self.model)
439
def outlineView_objectValueForTableColumn_byItem_(self, view, column,
441
return get_column_data(item.values, column)
443
def outlineView_writeItems_toPasteboard_(self, outline_view, items,
445
data = [i.values for i in items]
446
return self.view_writeColumnData_ToPasteboard_(outline_view, data,
449
def outlineView_validateDrop_proposedItem_proposedChildIndex_(self,
450
outlineview, drag_info, item, child_index):
454
iter = self.model.iter_for_item[item]
455
return self.validateDrop_dragInfo_parentIter_position_( outlineview,
456
drag_info, iter, child_index)
458
def outlineView_acceptDrop_item_childIndex_(self, outlineview, drag_info,
463
iter = self.model.iter_for_item[item]
464
return self.acceptDrop_dragInfo_parentIter_position_(outlineview,
465
drag_info, iter, child_index)