// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "chrome/browser/ui/cocoa/table_model_array_controller.h" #include "base/logging.h" #include "base/sys_string_conversions.h" #include "chrome/browser/remove_rows_table_model.h" #include "ui/base/models/table_model.h" @interface TableModelArrayController () - (NSUInteger)offsetForGroupID:(int)groupID; - (NSUInteger)offsetForGroupID:(int)groupID startingOffset:(NSUInteger)offset; - (NSIndexSet*)controllerRowsForModelRowsInRange:(NSRange)range; - (void)setModelRows:(RemoveRowsTableModel::Rows*)modelRows fromControllerRows:(NSIndexSet*)rows; - (void)modelDidChange; - (void)modelDidAddItemsInRange:(NSRange)range; - (void)modelDidRemoveItemsInRange:(NSRange)range; - (NSDictionary*)columnValuesForRow:(NSInteger)row; @end // Observer for a RemoveRowsTableModel. class RemoveRowsObserverBridge : public ui::TableModelObserver { public: RemoveRowsObserverBridge(TableModelArrayController* controller) : controller_(controller) {} virtual ~RemoveRowsObserverBridge() {} // ui::TableModelObserver methods virtual void OnModelChanged(); virtual void OnItemsChanged(int start, int length); virtual void OnItemsAdded(int start, int length); virtual void OnItemsRemoved(int start, int length); private: TableModelArrayController* controller_; // weak }; void RemoveRowsObserverBridge::OnModelChanged() { [controller_ modelDidChange]; } void RemoveRowsObserverBridge::OnItemsChanged(int start, int length) { OnItemsRemoved(start, length); OnItemsAdded(start, length); } void RemoveRowsObserverBridge::OnItemsAdded(int start, int length) { [controller_ modelDidAddItemsInRange:NSMakeRange(start, length)]; } void RemoveRowsObserverBridge::OnItemsRemoved(int start, int length) { [controller_ modelDidRemoveItemsInRange:NSMakeRange(start, length)]; } @implementation TableModelArrayController static NSString* const kIsGroupRow = @"_is_group_row"; static NSString* const kGroupID = @"_group_id"; - (void)bindToTableModel:(RemoveRowsTableModel*)model withColumns:(NSDictionary*)columns groupTitleColumn:(NSString*)groupTitleColumn { model_ = model; tableObserver_.reset(new RemoveRowsObserverBridge(self)); columns_.reset([columns copy]); groupTitle_.reset([groupTitleColumn copy]); model_->SetObserver(tableObserver_.get()); [self modelDidChange]; } - (void)modelDidChange { NSIndexSet* indexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [[self arrangedObjects] count])]; [self removeObjectsAtArrangedObjectIndexes:indexes]; if (model_->HasGroups()) { const ui::TableModel::Groups& groups = model_->GetGroups(); DCHECK(groupTitle_.get()); for (ui::TableModel::Groups::const_iterator it = groups.begin(); it != groups.end(); ++it) { NSDictionary* group = [NSDictionary dictionaryWithObjectsAndKeys: base::SysUTF16ToNSString(it->title), groupTitle_.get(), [NSNumber numberWithBool:YES], kIsGroupRow, nil]; [self addObject:group]; } } [self modelDidAddItemsInRange:NSMakeRange(0, model_->RowCount())]; } - (NSUInteger)offsetForGroupID:(int)groupID startingOffset:(NSUInteger)offset { const ui::TableModel::Groups& groups = model_->GetGroups(); DCHECK_GT(offset, 0u); for (NSUInteger i = offset - 1; i < groups.size(); ++i) { if (groups[i].id == groupID) return i + 1; } NOTREACHED(); return NSNotFound; } - (NSUInteger)offsetForGroupID:(int)groupID { return [self offsetForGroupID:groupID startingOffset:1]; } - (int)groupIDForControllerRow:(NSUInteger)row { NSDictionary* values = [[self arrangedObjects] objectAtIndex:row]; return [[values objectForKey:kGroupID] intValue]; } - (void)setModelRows:(RemoveRowsTableModel::Rows*)modelRows fromControllerRows:(NSIndexSet*)rows { if ([rows count] == 0) return; if (!model_->HasGroups()) { for (NSUInteger i = [rows firstIndex]; i != NSNotFound; i = [rows indexGreaterThanIndex:i]) { modelRows->insert(i); } return; } NSUInteger offset = 1; for (NSUInteger i = [rows firstIndex]; i != NSNotFound; i = [rows indexGreaterThanIndex:i]) { int group = [self groupIDForControllerRow:i]; offset = [self offsetForGroupID:group startingOffset:offset]; modelRows->insert(i - offset); } } - (NSIndexSet*)controllerRowsForModelRowsInRange:(NSRange)range { if (!model_->HasGroups()) return [NSIndexSet indexSetWithIndexesInRange:range]; NSMutableIndexSet* indexes = [NSMutableIndexSet indexSet]; NSUInteger offset = 1; for (NSUInteger i = range.location; i < NSMaxRange(range); ++i) { int group = model_->GetGroupID(i); offset = [self offsetForGroupID:group startingOffset:offset]; [indexes addIndex:i + offset]; } return indexes; } - (void)modelDidAddItemsInRange:(NSRange)range { if (range.length == 0) return; NSMutableArray* rows = [NSMutableArray arrayWithCapacity:range.length]; for (NSUInteger i = range.location; i < NSMaxRange(range); ++i) [rows addObject:[self columnValuesForRow:i]]; NSIndexSet* indexes = [self controllerRowsForModelRowsInRange:range]; [self insertObjects:rows atArrangedObjectIndexes:indexes]; } - (void)modelDidRemoveItemsInRange:(NSRange)range { if (range.length == 0) return; NSMutableIndexSet* indexes = [NSMutableIndexSet indexSetWithIndexesInRange:range]; if (model_->HasGroups()) { // When this method is called, the model has already removed items, so // accessing items in the model from |range.location| on may not be possible // anymore. Therefore we use the item right before that, if it exists. NSUInteger offset = 0; if (range.location > 0) { int last_group = model_->GetGroupID(range.location - 1); offset = [self offsetForGroupID:last_group]; } [indexes shiftIndexesStartingAtIndex:0 by:offset]; for (NSUInteger row = range.location + offset; row < NSMaxRange(range) + offset; ++row) { if ([self tableView:nil isGroupRow:row]) { // Skip over group rows. [indexes shiftIndexesStartingAtIndex:row by:1]; offset++; } } } [self removeObjectsAtArrangedObjectIndexes:indexes]; } - (NSDictionary*)columnValuesForRow:(NSInteger)row { NSMutableDictionary* dict = [NSMutableDictionary dictionary]; if (model_->HasGroups()) { [dict setObject:[NSNumber numberWithInt:model_->GetGroupID(row)] forKey:kGroupID]; } for (NSString* identifier in columns_.get()) { int column_id = [[columns_ objectForKey:identifier] intValue]; string16 text = model_->GetText(row, column_id); [dict setObject:base::SysUTF16ToNSString(text) forKey:identifier]; } return dict; } #pragma mark Overridden from NSArrayController - (BOOL)canRemove { if (!model_) return NO; RemoveRowsTableModel::Rows rows; [self setModelRows:&rows fromControllerRows:[self selectionIndexes]]; return model_->CanRemoveRows(rows); } - (IBAction)remove:(id)sender { RemoveRowsTableModel::Rows rows; [self setModelRows:&rows fromControllerRows:[self selectionIndexes]]; model_->RemoveRows(rows); } #pragma mark NSTableView delegate methods - (BOOL)tableView:(NSTableView*)tableView isGroupRow:(NSInteger)row { NSDictionary* values = [[self arrangedObjects] objectAtIndex:row]; return [[values objectForKey:kIsGroupRow] boolValue]; } - (NSIndexSet*)tableView:(NSTableView*)tableView selectionIndexesForProposedSelection:(NSIndexSet*)proposedIndexes { NSMutableIndexSet* indexes = [proposedIndexes mutableCopy]; for (NSUInteger i = [proposedIndexes firstIndex]; i != NSNotFound; i = [proposedIndexes indexGreaterThanIndex:i]) { if ([self tableView:tableView isGroupRow:i]) { [indexes removeIndex:i]; NSUInteger row = i + 1; while (row < [[self arrangedObjects] count] && ![self tableView:tableView isGroupRow:row]) [indexes addIndex:row++]; } } return indexes; } #pragma mark Actions - (IBAction)removeAll:(id)sender { model_->RemoveAll(); } @end