~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to osx/plat/frontends/widgets/tablemodel.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Miro - an RSS based video player application
 
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
 
3
#
 
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.
 
8
#
 
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.
 
13
#
 
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
 
17
#
 
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
 
20
# library.
 
21
#
 
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.
 
28
 
 
29
"""tablemodel.py -- Model classes for TableView.  """
 
30
 
 
31
import itertools
 
32
 
 
33
from AppKit import NSDragOperationNone, NSTableViewDropOn, protocols
 
34
from Foundation import NSObject, NSNotFound
 
35
from objc import YES, NO, nil
 
36
 
 
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
 
41
 
 
42
def list_from_nsindexset(index_set):
 
43
    rows = list()
 
44
    index = index_set.firstIndex()
 
45
    while (index != NSNotFound):
 
46
        rows.append(index)
 
47
        index = index_set.indexGreaterThanIndex_(index)
 
48
    return rows
 
49
 
 
50
class RowList(object):
 
51
    """RowList is a Linked list that has some optimizations for looking up
 
52
    rows by index number.
 
53
    """
 
54
    def __init__(self):
 
55
        self.list = fasttypes.LinkedList()
 
56
        self.iter_cache = []
 
57
 
 
58
    def firstIter(self):
 
59
        return self.list.firstIter()
 
60
 
 
61
    def lastIter(self):
 
62
        return self.list.lastIter()
 
63
 
 
64
    def insertBefore(self, iter, value):
 
65
        self.iter_cache = []
 
66
        if iter is None:
 
67
            return self.list.append(value)
 
68
        else:
 
69
            return self.list.insertBefore(iter, value)
 
70
        
 
71
    def append(self, value):
 
72
        return self.list.append(value)
 
73
 
 
74
    def __len__(self):
 
75
        return len(self.list)
 
76
 
 
77
    def __getitem__(self, iter):
 
78
        return self.list[iter]
 
79
 
 
80
    def __iter__(self):
 
81
        iter = self.firstIter()
 
82
        while iter != self.lastIter():
 
83
            yield iter.value()
 
84
            iter.forward()
 
85
 
 
86
    def remove(self, iter):
 
87
        self.iter_cache = []
 
88
        return self.list.remove(iter)
 
89
 
 
90
    def nth_iter(self, index):
 
91
        if index < 0:
 
92
            raise IndexError()
 
93
        elif index >= len(self):
 
94
            return None
 
95
        if len(self.iter_cache) == 0:
 
96
            self.iter_cache.append(self.firstIter())
 
97
        try:
 
98
            return self.iter_cache[index].copy()
 
99
        except IndexError:
 
100
            pass
 
101
        iter = self.iter_cache[-1].copy()
 
102
        index -= len(self.iter_cache) - 1
 
103
        for x in xrange(index):
 
104
            iter.forward()
 
105
            self.iter_cache.append(iter.copy())
 
106
        return iter
 
107
 
 
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')
 
117
 
 
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
 
122
 
 
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)
 
127
 
 
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)
 
132
 
 
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():
 
138
            rv = None
 
139
        return rv
 
140
 
 
141
    def nth_iter(self, index):
 
142
        return self.row_list.nth_iter(index)
 
143
 
 
144
    def next_iter(self, iter):
 
145
        row_list = self.containing_list(iter)
 
146
        retval = iter.copy()
 
147
        retval.forward()
 
148
        if retval == row_list.lastIter():
 
149
            return None
 
150
        else:
 
151
            return retval
 
152
 
 
153
    def first_iter(self):
 
154
        if len(self.row_list) > 0:
 
155
            return self.row_list.firstIter()
 
156
        else:
 
157
            return None
 
158
 
 
159
    def __len__(self):
 
160
        return len(self.row_list)
 
161
 
 
162
    def __getitem__(self, iter):
 
163
        return iter.value()
 
164
 
 
165
    def __iter__(self):
 
166
        return iter(self.row_list)
 
167
 
 
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)
 
172
 
 
173
    def update_values(self, column_values):
 
174
        self.values = list(column_values)
 
175
 
 
176
    def __getitem__(self, index):
 
177
        return self.values[index]
 
178
 
 
179
    def __len__(self):
 
180
        return len(self.values)
 
181
 
 
182
    def __iter__(self):
 
183
        return iter(self.values)
 
184
 
 
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 = {}
 
190
 
 
191
    def remember_row_at_index(self, row, index):
 
192
        if row not in self.row_indexes:
 
193
            self.row_indexes[row] = index
 
194
 
 
195
    def get_index_of_row(self, row):
 
196
        try:
 
197
            return self.row_indexes[row]
 
198
        except KeyError:
 
199
            iter = self.row_list.firstIter()
 
200
            index = 0
 
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:
 
205
                    return index
 
206
                index += 1
 
207
                iter.forward()
 
208
            raise LookupError("%s is not in this table" % row)
 
209
 
 
210
    def containing_list(self, iter):
 
211
        return self.row_list
 
212
 
 
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)
 
217
        return retval
 
218
 
 
219
    def remove(self, iter):
 
220
        self.row_indexes = {}
 
221
        return TableModelBase.remove(self, iter)
 
222
 
 
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)
 
228
        return retval
 
229
 
 
230
    def iter_for_row(self, tableview, row):
 
231
        return self.row_list.nth_iter(row)
 
232
 
 
233
class TreeNode(NSObject, TableRow):
 
234
    """A row in a TreeTableModel"""
 
235
 
 
236
    # Implementation note: these need to be NSObjects because we return them 
 
237
    # to the NSOutlineView.
 
238
 
 
239
    def initWithValues_parent_(self, column_values, parent):
 
240
        self.children = RowList()
 
241
        self.update_values(column_values)
 
242
        self.parent = parent
 
243
        return self
 
244
 
 
245
    @staticmethod
 
246
    def create_(values, parent):
 
247
        return TreeNode.alloc().initWithValues_parent_(values, parent)
 
248
 
 
249
    def iterchildren(self):
 
250
        return iter(self.children)
 
251
 
 
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 = {}
 
257
 
 
258
    def containing_list(self, iter):
 
259
        return self.row_list_for_iter(iter.value().parent)
 
260
 
 
261
    def row_list_for_iter(self, iter):
 
262
        if iter is None:
 
263
            return self.row_list
 
264
        else:
 
265
            return iter.value().children
 
266
 
 
267
    def remember_iter(self, iter):
 
268
        self.iter_for_item[iter.value()] = iter
 
269
        return iter
 
270
 
 
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)
 
275
 
 
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)
 
280
 
 
281
    def remove(self, iter):
 
282
        self.forget_iter_for_item(iter.value())
 
283
        return TableModelBase.remove(self, iter)
 
284
 
 
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)
 
290
 
 
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)
 
296
 
 
297
    def child_iter(self, iter):
 
298
        row_list = iter.value().children
 
299
        if len(row_list) == 0:
 
300
            return None
 
301
        else:
 
302
            return row_list.firstIter()
 
303
 
 
304
    def nth_child_iter(self, iter, index):
 
305
        row_list = self.row_list_for_iter(iter)
 
306
        return row_list.nth_iter(index)
 
307
 
 
308
    def has_child(self, iter):
 
309
        return  len(iter.value().children) > 0
 
310
 
 
311
    def children_count(self, iter):
 
312
        if iter is not None:
 
313
            return len(iter.value().children)
 
314
        else:
 
315
            return len(self.row_list)
 
316
 
 
317
    def children_iters(self, iter):
 
318
        return self.iters_in_rowlist(self.row_list_for_iter(iter))
 
319
 
 
320
    def parent_iter(self, iter):
 
321
        return iter.value().parent
 
322
 
 
323
    def iter_for_row(self, tableview, row):
 
324
        return self.iter_for_item[tableview.itemAtRow_(row)]
 
325
 
 
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())
 
329
 
 
330
class DataSourceBase(NSObject):
 
331
    def initWithModel_(self, model):
 
332
        self.model = model
 
333
        self.drag_source = None
 
334
        self.drag_dest = None
 
335
 
 
336
    def setDragSource_(self, drag_source):
 
337
        self.drag_source = drag_source
 
338
 
 
339
    def setDragDest_(self, drag_dest):
 
340
        self.drag_dest = drag_dest
 
341
 
 
342
    def view_writeColumnData_ToPasteboard_(self, view, data, pasteboard):
 
343
        if not self.drag_source:
 
344
            return NO
 
345
        wrapper = wrappermap.wrapper(view)
 
346
        drag_data = self.drag_source.begin_drag(wrapper, data)
 
347
        if not drag_data:
 
348
            return NO
 
349
        pasteboard.declareTypes_owner_(drag_data.keys(), self)
 
350
        for type, value in drag_data.items():
 
351
            pasteboard.setString_forType_(value, type)
 
352
        return YES
 
353
 
 
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)):
 
358
            return None
 
359
        types = self.drag_dest.allowed_types()
 
360
        return drag_info.draggingPasteboard().availableTypeFromArray_(types)
 
361
 
 
362
    def validateDrop_dragInfo_parentIter_position_(self, view, drag_info, 
 
363
            parent, position):
 
364
        type = self.calcType_(drag_info)
 
365
        if type:
 
366
            wrapper = wrappermap.wrapper(view)
 
367
            return self.drag_dest.validate_drop(wrapper, self.model, type,
 
368
                    drag_info.draggingSourceOperationMask(), parent,
 
369
                    position)
 
370
        else:
 
371
            return NSDragOperationNone
 
372
 
 
373
    def acceptDrop_dragInfo_parentIter_position_(self, view, drag_info,
 
374
            parent, position):
 
375
        type = self.calcType_(drag_info)
 
376
        if type:
 
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,
 
381
                    position, data)
 
382
        else:
 
383
            return NO
 
384
 
 
385
class MiroTableViewDataSource(DataSourceBase, protocols.NSTableDataSource):
 
386
    def numberOfRowsInTableView_(self, table_view):
 
387
        return len(self.model)
 
388
 
 
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)
 
393
 
 
394
    def tableView_writeRowsWithIndexes_toPasteboard_(self, tableview, rowIndexes,
 
395
            pasteboard):
 
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, 
 
399
                pasteboard)
 
400
 
 
401
    def translateRow_operation_(self, row, operation):
 
402
        if operation == NSTableViewDropOn:
 
403
            return (self.model.nth_iter(row),), -1
 
404
        else:
 
405
            return None, row
 
406
 
 
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)
 
412
 
 
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)
 
418
 
 
419
class MiroOutlineViewDataSource(DataSourceBase, protocols.NSOutlineViewDataSource):
 
420
    def outlineView_child_ofItem_(self, view, child, item):
 
421
        if item is nil:
 
422
            row_list = self.model.row_list
 
423
        else:
 
424
            row_list = item.children
 
425
        return row_list.nth_iter(child).value()
 
426
 
 
427
    def outlineView_isItemExpandable_(self, view, item):
 
428
        if item is not nil and hasattr(item, 'children'):
 
429
            return len(item.children) > 0
 
430
        else:
 
431
            return len(self.model) > 0
 
432
 
 
433
    def outlineView_numberOfChildrenOfItem_(self, view, item):
 
434
        if item is not nil and hasattr(item, 'children'):
 
435
            return len(item.children)
 
436
        else:
 
437
            return len(self.model)
 
438
 
 
439
    def outlineView_objectValueForTableColumn_byItem_(self, view, column,
 
440
            item):
 
441
        return get_column_data(item.values, column)
 
442
 
 
443
    def outlineView_writeItems_toPasteboard_(self, outline_view, items, 
 
444
            pasteboard):
 
445
        data = [i.values for i in items]
 
446
        return self.view_writeColumnData_ToPasteboard_(outline_view, data, 
 
447
                pasteboard)
 
448
 
 
449
    def outlineView_validateDrop_proposedItem_proposedChildIndex_(self,
 
450
            outlineview, drag_info, item, child_index):
 
451
        if item is None:
 
452
            iter = None
 
453
        else:
 
454
            iter = self.model.iter_for_item[item]
 
455
        return self.validateDrop_dragInfo_parentIter_position_( outlineview,
 
456
                drag_info, iter, child_index)
 
457
 
 
458
    def outlineView_acceptDrop_item_childIndex_(self, outlineview, drag_info,
 
459
            item, child_index):
 
460
        if item is None:
 
461
            iter = None
 
462
        else:
 
463
            iter = self.model.iter_for_item[item]
 
464
        return self.acceptDrop_dragInfo_parentIter_position_(outlineview, 
 
465
                drag_info, iter, child_index)