1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/cocoa/task_manager_mac.h" 6 7 #include <algorithm> 8 #include <vector> 9 10 #include "base/mac/mac_util.h" 11 #include "base/sys_string_conversions.h" 12 #include "chrome/browser/browser_process.h" 13 #import "chrome/browser/ui/cocoa/window_size_autosaver.h" 14 #include "chrome/common/pref_names.h" 15 #include "grit/generated_resources.h" 16 #include "third_party/skia/include/core/SkBitmap.h" 17 #include "ui/base/l10n/l10n_util_mac.h" 18 19 namespace { 20 21 // Width of "a" and most other letters/digits in "small" table views. 22 const int kCharWidth = 6; 23 24 // Some of the strings below have spaces at the end or are missing letters, to 25 // make the columns look nicer, and to take potentially longer localized strings 26 // into account. 27 const struct ColumnWidth { 28 int columnId; 29 int minWidth; 30 int maxWidth; // If this is -1, 1.5*minColumWidth is used as max width. 31 } columnWidths[] = { 32 // Note that arraysize includes the trailing \0. That's intended. 33 { IDS_TASK_MANAGER_PAGE_COLUMN, 120, 600 }, 34 { IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN, 35 arraysize("800 MiB") * kCharWidth, -1 }, 36 { IDS_TASK_MANAGER_SHARED_MEM_COLUMN, 37 arraysize("800 MiB") * kCharWidth, -1 }, 38 { IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN, 39 arraysize("800 MiB") * kCharWidth, -1 }, 40 { IDS_TASK_MANAGER_CPU_COLUMN, 41 arraysize("99.9") * kCharWidth, -1 }, 42 { IDS_TASK_MANAGER_NET_COLUMN, 43 arraysize("150 kiB/s") * kCharWidth, -1 }, 44 { IDS_TASK_MANAGER_PROCESS_ID_COLUMN, 45 arraysize("73099 ") * kCharWidth, -1 }, 46 { IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN, 47 arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 }, 48 { IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN, 49 arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 }, 50 { IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN, 51 arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 }, 52 { IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN, 53 arraysize("800 kB") * kCharWidth, -1 }, 54 { IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN, 55 arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 }, 56 { IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN, 57 arraysize("15 ") * kCharWidth, -1 }, 58 }; 59 60 class SortHelper { 61 public: 62 SortHelper(TaskManagerModel* model, NSSortDescriptor* column) 63 : sort_column_([[column key] intValue]), 64 ascending_([column ascending]), 65 model_(model) {} 66 67 bool operator()(int a, int b) { 68 std::pair<int, int> group_range1 = model_->GetGroupRangeForResource(a); 69 std::pair<int, int> group_range2 = model_->GetGroupRangeForResource(b); 70 if (group_range1 == group_range2) { 71 // The two rows are in the same group, sort so that items in the same 72 // group always appear in the same order. |ascending_| is intentionally 73 // ignored. 74 return a < b; 75 } 76 // Sort by the first entry of each of the groups. 77 int cmp_result = model_->CompareValues( 78 group_range1.first, group_range2.first, sort_column_); 79 if (!ascending_) 80 cmp_result = -cmp_result; 81 return cmp_result < 0; 82 } 83 private: 84 int sort_column_; 85 bool ascending_; 86 TaskManagerModel* model_; // weak; 87 }; 88 89 } // namespace 90 91 @interface TaskManagerWindowController (Private) 92 - (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible; 93 - (void)setUpTableColumns; 94 - (void)setUpTableHeaderContextMenu; 95 - (void)toggleColumn:(id)sender; 96 - (void)adjustSelectionAndEndProcessButton; 97 - (void)deselectRows; 98 @end 99 100 //////////////////////////////////////////////////////////////////////////////// 101 // TaskManagerWindowController implementation: 102 103 @implementation TaskManagerWindowController 104 105 - (id)initWithTaskManagerObserver:(TaskManagerMac*)taskManagerObserver 106 highlightBackgroundResources:(bool)highlightBackgroundResources { 107 NSString* nibpath = [base::mac::MainAppBundle() 108 pathForResource:@"TaskManager" 109 ofType:@"nib"]; 110 if ((self = [super initWithWindowNibPath:nibpath owner:self])) { 111 taskManagerObserver_ = taskManagerObserver; 112 taskManager_ = taskManagerObserver_->task_manager(); 113 model_ = taskManager_->model(); 114 highlightBackgroundResources_ = highlightBackgroundResources; 115 if (highlightBackgroundResources_) { 116 // Highlight background resources with a yellow background. 117 backgroundResourceColor_.reset( 118 [[NSColor colorWithDeviceRed:0xff/255.0 119 green:0xfa/255.0 120 blue:0xcd/255.0 121 alpha:1.0] retain]); 122 } 123 124 if (g_browser_process && g_browser_process->local_state()) { 125 size_saver_.reset([[WindowSizeAutosaver alloc] 126 initWithWindow:[self window] 127 prefService:g_browser_process->local_state() 128 path:prefs::kTaskManagerWindowPlacement]); 129 } 130 [self showWindow:self]; 131 } 132 return self; 133 } 134 135 - (void)sortShuffleArray { 136 viewToModelMap_.resize(model_->ResourceCount()); 137 for (size_t i = 0; i < viewToModelMap_.size(); ++i) 138 viewToModelMap_[i] = i; 139 140 std::sort(viewToModelMap_.begin(), viewToModelMap_.end(), 141 SortHelper(model_, currentSortDescriptor_.get())); 142 143 modelToViewMap_.resize(viewToModelMap_.size()); 144 for (size_t i = 0; i < viewToModelMap_.size(); ++i) 145 modelToViewMap_[viewToModelMap_[i]] = i; 146 } 147 148 - (void)reloadData { 149 // Store old view indices, and the model indices they map to. 150 NSIndexSet* viewSelection = [tableView_ selectedRowIndexes]; 151 std::vector<int> modelSelection; 152 for (NSUInteger i = [viewSelection lastIndex]; 153 i != NSNotFound; 154 i = [viewSelection indexLessThanIndex:i]) { 155 modelSelection.push_back(viewToModelMap_[i]); 156 } 157 158 // Sort. 159 [self sortShuffleArray]; 160 161 // Use the model indices to get the new view indices of the selection, and 162 // set selection to that. This assumes that no rows were added or removed 163 // (in that case, the selection is cleared before -reloadData is called). 164 if (!modelSelection.empty()) 165 DCHECK_EQ([tableView_ numberOfRows], model_->ResourceCount()); 166 NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet]; 167 for (size_t i = 0; i < modelSelection.size(); ++i) 168 [indexSet addIndex:modelToViewMap_[modelSelection[i]]]; 169 [tableView_ selectRowIndexes:indexSet byExtendingSelection:NO]; 170 171 [tableView_ reloadData]; 172 [self adjustSelectionAndEndProcessButton]; 173 } 174 175 - (IBAction)statsLinkClicked:(id)sender { 176 TaskManager::GetInstance()->OpenAboutMemory(); 177 } 178 179 - (IBAction)killSelectedProcesses:(id)sender { 180 NSIndexSet* selection = [tableView_ selectedRowIndexes]; 181 for (NSUInteger i = [selection lastIndex]; 182 i != NSNotFound; 183 i = [selection indexLessThanIndex:i]) { 184 taskManager_->KillProcess(viewToModelMap_[i]); 185 } 186 } 187 188 - (void)selectDoubleClickedTab:(id)sender { 189 NSInteger row = [tableView_ clickedRow]; 190 if (row < 0) 191 return; // Happens e.g. if the table header is double-clicked. 192 taskManager_->ActivateProcess(viewToModelMap_[row]); 193 } 194 195 - (NSTableView*)tableView { 196 return tableView_; 197 } 198 199 - (void)awakeFromNib { 200 [self setUpTableColumns]; 201 [self setUpTableHeaderContextMenu]; 202 [self adjustSelectionAndEndProcessButton]; 203 204 [tableView_ setDoubleAction:@selector(selectDoubleClickedTab:)]; 205 [tableView_ setIntercellSpacing:NSMakeSize(0.0, 0.0)]; 206 [tableView_ sizeToFit]; 207 } 208 209 - (void)dealloc { 210 [tableView_ setDelegate:nil]; 211 [tableView_ setDataSource:nil]; 212 [super dealloc]; 213 } 214 215 // Adds a column which has the given string id as title. |isVisible| specifies 216 // if the column is initially visible. 217 - (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible { 218 scoped_nsobject<NSTableColumn> column([[NSTableColumn alloc] 219 initWithIdentifier:[NSNumber numberWithInt:columnId]]); 220 221 NSTextAlignment textAlignment = columnId == IDS_TASK_MANAGER_PAGE_COLUMN ? 222 NSLeftTextAlignment : NSRightTextAlignment; 223 224 [[column.get() headerCell] 225 setStringValue:l10n_util::GetNSStringWithFixup(columnId)]; 226 [[column.get() headerCell] setAlignment:textAlignment]; 227 [[column.get() dataCell] setAlignment:textAlignment]; 228 229 NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; 230 [[column.get() dataCell] setFont:font]; 231 232 [column.get() setHidden:!isVisible]; 233 [column.get() setEditable:NO]; 234 235 // The page column should by default be sorted ascending. 236 BOOL ascending = columnId == IDS_TASK_MANAGER_PAGE_COLUMN; 237 238 scoped_nsobject<NSSortDescriptor> sortDescriptor([[NSSortDescriptor alloc] 239 initWithKey:[NSString stringWithFormat:@"%d", columnId] 240 ascending:ascending]); 241 [column.get() setSortDescriptorPrototype:sortDescriptor.get()]; 242 243 // Default values, only used in release builds if nobody notices the DCHECK 244 // during development when adding new columns. 245 int minWidth = 200, maxWidth = 400; 246 247 size_t i; 248 for (i = 0; i < arraysize(columnWidths); ++i) { 249 if (columnWidths[i].columnId == columnId) { 250 minWidth = columnWidths[i].minWidth; 251 maxWidth = columnWidths[i].maxWidth; 252 if (maxWidth < 0) 253 maxWidth = 3 * minWidth / 2; // *1.5 for ints. 254 break; 255 } 256 } 257 DCHECK(i < arraysize(columnWidths)) << "Could not find " << columnId; 258 [column.get() setMinWidth:minWidth]; 259 [column.get() setMaxWidth:maxWidth]; 260 [column.get() setResizingMask:NSTableColumnAutoresizingMask | 261 NSTableColumnUserResizingMask]; 262 263 [tableView_ addTableColumn:column.get()]; 264 return column.get(); // Now retained by |tableView_|. 265 } 266 267 // Adds all the task manager's columns to the table. 268 - (void)setUpTableColumns { 269 for (NSTableColumn* column in [tableView_ tableColumns]) 270 [tableView_ removeTableColumn:column]; 271 NSTableColumn* nameColumn = [self addColumnWithId:IDS_TASK_MANAGER_PAGE_COLUMN 272 visible:YES]; 273 // |nameColumn| displays an icon for every row -- this is done by an 274 // NSButtonCell. 275 scoped_nsobject<NSButtonCell> nameCell( 276 [[NSButtonCell alloc] initTextCell:@""]); 277 [nameCell.get() setImagePosition:NSImageLeft]; 278 [nameCell.get() setButtonType:NSSwitchButton]; 279 [nameCell.get() setAlignment:[[nameColumn dataCell] alignment]]; 280 [nameCell.get() setFont:[[nameColumn dataCell] font]]; 281 [nameColumn setDataCell:nameCell.get()]; 282 283 // Initially, sort on the tab name. 284 [tableView_ setSortDescriptors: 285 [NSArray arrayWithObject:[nameColumn sortDescriptorPrototype]]]; 286 287 [self addColumnWithId:IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN visible:YES]; 288 [self addColumnWithId:IDS_TASK_MANAGER_SHARED_MEM_COLUMN visible:NO]; 289 [self addColumnWithId:IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN visible:NO]; 290 [self addColumnWithId:IDS_TASK_MANAGER_CPU_COLUMN visible:YES]; 291 [self addColumnWithId:IDS_TASK_MANAGER_NET_COLUMN visible:YES]; 292 [self addColumnWithId:IDS_TASK_MANAGER_PROCESS_ID_COLUMN visible:NO]; 293 [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN 294 visible:NO]; 295 [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN 296 visible:NO]; 297 [self addColumnWithId:IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN visible:NO]; 298 [self addColumnWithId:IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN visible:NO]; 299 [self addColumnWithId:IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN 300 visible:NO]; 301 [self addColumnWithId:IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN visible:NO]; 302 } 303 304 // Creates a context menu for the table header that allows the user to toggle 305 // which columns should be shown and which should be hidden (like e.g. 306 // Task Manager.app's table header context menu). 307 - (void)setUpTableHeaderContextMenu { 308 scoped_nsobject<NSMenu> contextMenu( 309 [[NSMenu alloc] initWithTitle:@"Task Manager context menu"]); 310 for (NSTableColumn* column in [tableView_ tableColumns]) { 311 NSMenuItem* item = [contextMenu.get() 312 addItemWithTitle:[[column headerCell] stringValue] 313 action:@selector(toggleColumn:) 314 keyEquivalent:@""]; 315 [item setTarget:self]; 316 [item setRepresentedObject:column]; 317 [item setState:[column isHidden] ? NSOffState : NSOnState]; 318 } 319 [[tableView_ headerView] setMenu:contextMenu.get()]; 320 } 321 322 // Callback for the table header context menu. Toggles visibility of the table 323 // column associated with the clicked menu item. 324 - (void)toggleColumn:(id)item { 325 DCHECK([item isKindOfClass:[NSMenuItem class]]); 326 if (![item isKindOfClass:[NSMenuItem class]]) 327 return; 328 329 NSTableColumn* column = [item representedObject]; 330 DCHECK(column); 331 NSInteger oldState = [item state]; 332 NSInteger newState = oldState == NSOnState ? NSOffState : NSOnState; 333 [column setHidden:newState == NSOffState]; 334 [item setState:newState]; 335 [tableView_ sizeToFit]; 336 [tableView_ setNeedsDisplay]; 337 } 338 339 // This function appropriately sets the enabled states on the table's editing 340 // buttons. 341 - (void)adjustSelectionAndEndProcessButton { 342 bool selectionContainsBrowserProcess = false; 343 344 // If a row is selected, make sure that all rows belonging to the same process 345 // are selected as well. Also, check if the selection contains the browser 346 // process. 347 NSIndexSet* selection = [tableView_ selectedRowIndexes]; 348 for (NSUInteger i = [selection lastIndex]; 349 i != NSNotFound; 350 i = [selection indexLessThanIndex:i]) { 351 int modelIndex = viewToModelMap_[i]; 352 if (taskManager_->IsBrowserProcess(modelIndex)) 353 selectionContainsBrowserProcess = true; 354 355 std::pair<int, int> rangePair = 356 model_->GetGroupRangeForResource(modelIndex); 357 NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet]; 358 for (int j = 0; j < rangePair.second; ++j) 359 [indexSet addIndex:modelToViewMap_[rangePair.first + j]]; 360 [tableView_ selectRowIndexes:indexSet byExtendingSelection:YES]; 361 } 362 363 bool enabled = [selection count] > 0 && !selectionContainsBrowserProcess; 364 [endProcessButton_ setEnabled:enabled]; 365 } 366 367 - (void)deselectRows { 368 [tableView_ deselectAll:self]; 369 } 370 371 // Table view delegate method. 372 - (void)tableViewSelectionIsChanging:(NSNotification*)aNotification { 373 [self adjustSelectionAndEndProcessButton]; 374 } 375 376 - (void)windowWillClose:(NSNotification*)notification { 377 if (taskManagerObserver_) { 378 taskManagerObserver_->WindowWasClosed(); 379 taskManagerObserver_ = nil; 380 } 381 [self autorelease]; 382 } 383 384 // Delegate method invoked before each cell in the table is displayed. We 385 // override this to provide highlighting of background resources. 386 - (void) tableView:(NSTableView*)tableView 387 willDisplayCell:(id)cell 388 forTableColumn:(NSTableColumn*)tableColumn 389 row:(NSInteger)row { 390 if (!highlightBackgroundResources_) 391 return; 392 393 DCHECK([cell respondsToSelector:@selector(setBackgroundColor:)]); 394 if ([cell respondsToSelector:@selector(setBackgroundColor:)]) { 395 NSColor* color = nil; 396 if (taskManagerObserver_->IsBackgroundRow(viewToModelMap_[row]) && 397 ![tableView isRowSelected:row]) { 398 color = backgroundResourceColor_.get(); 399 if ((row % 2) == 1 && [tableView usesAlternatingRowBackgroundColors]) { 400 color = [color blendedColorWithFraction:0.05 401 ofColor:[NSColor blackColor]]; 402 } 403 } 404 [cell setBackgroundColor:color]; 405 406 // The icon at the left is an |NSButtonCell|, which does not 407 // implement this method on 10.5. 408 if ([cell respondsToSelector:@selector(setDrawsBackground:)]) 409 [cell setDrawsBackground:(color != nil)]; 410 } 411 } 412 413 @end 414 415 @implementation TaskManagerWindowController (NSTableDataSource) 416 417 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView { 418 DCHECK(tableView == tableView_ || tableView_ == nil); 419 return model_->ResourceCount(); 420 } 421 422 - (NSString*)modelTextForRow:(int)row column:(int)columnId { 423 DCHECK_LT(static_cast<size_t>(row), viewToModelMap_.size()); 424 row = viewToModelMap_[row]; 425 switch (columnId) { 426 case IDS_TASK_MANAGER_PAGE_COLUMN: // Process 427 return base::SysUTF16ToNSString(model_->GetResourceTitle(row)); 428 429 case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN: // Memory 430 if (!model_->IsResourceFirstInGroup(row)) 431 return @""; 432 return base::SysUTF16ToNSString(model_->GetResourcePrivateMemory(row)); 433 434 case IDS_TASK_MANAGER_SHARED_MEM_COLUMN: // Memory 435 if (!model_->IsResourceFirstInGroup(row)) 436 return @""; 437 return base::SysUTF16ToNSString(model_->GetResourceSharedMemory(row)); 438 439 case IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN: // Memory 440 if (!model_->IsResourceFirstInGroup(row)) 441 return @""; 442 return base::SysUTF16ToNSString(model_->GetResourcePhysicalMemory(row)); 443 444 case IDS_TASK_MANAGER_CPU_COLUMN: // CPU 445 if (!model_->IsResourceFirstInGroup(row)) 446 return @""; 447 return base::SysUTF16ToNSString(model_->GetResourceCPUUsage(row)); 448 449 case IDS_TASK_MANAGER_NET_COLUMN: // Net 450 return base::SysUTF16ToNSString(model_->GetResourceNetworkUsage(row)); 451 452 case IDS_TASK_MANAGER_PROCESS_ID_COLUMN: // Process ID 453 if (!model_->IsResourceFirstInGroup(row)) 454 return @""; 455 return base::SysUTF16ToNSString(model_->GetResourceProcessId(row)); 456 457 case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN: // WebCore image cache 458 if (!model_->IsResourceFirstInGroup(row)) 459 return @""; 460 return base::SysUTF16ToNSString( 461 model_->GetResourceWebCoreImageCacheSize(row)); 462 463 case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN: // WebCore script cache 464 if (!model_->IsResourceFirstInGroup(row)) 465 return @""; 466 return base::SysUTF16ToNSString( 467 model_->GetResourceWebCoreScriptsCacheSize(row)); 468 469 case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN: // WebCore CSS cache 470 if (!model_->IsResourceFirstInGroup(row)) 471 return @""; 472 return base::SysUTF16ToNSString( 473 model_->GetResourceWebCoreCSSCacheSize(row)); 474 475 case IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN: 476 if (!model_->IsResourceFirstInGroup(row)) 477 return @""; 478 return base::SysUTF16ToNSString( 479 model_->GetResourceSqliteMemoryUsed(row)); 480 481 case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN: 482 if (!model_->IsResourceFirstInGroup(row)) 483 return @""; 484 return base::SysUTF16ToNSString( 485 model_->GetResourceV8MemoryAllocatedSize(row)); 486 487 case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN: // Goats Teleported! 488 return base::SysUTF16ToNSString(model_->GetResourceGoatsTeleported(row)); 489 490 default: 491 NOTREACHED(); 492 return @""; 493 } 494 } 495 496 - (id)tableView:(NSTableView*)tableView 497 objectValueForTableColumn:(NSTableColumn*)tableColumn 498 row:(NSInteger)rowIndex { 499 // NSButtonCells expect an on/off state as objectValue. Their title is set 500 // in |tableView:dataCellForTableColumn:row:| below. 501 if ([[tableColumn identifier] intValue] == IDS_TASK_MANAGER_PAGE_COLUMN) { 502 return [NSNumber numberWithInt:NSOffState]; 503 } 504 505 return [self modelTextForRow:rowIndex 506 column:[[tableColumn identifier] intValue]]; 507 } 508 509 - (NSCell*)tableView:(NSTableView*)tableView 510 dataCellForTableColumn:(NSTableColumn*)tableColumn 511 row:(NSInteger)rowIndex { 512 NSCell* cell = [tableColumn dataCellForRow:rowIndex]; 513 514 // Set the favicon and title for the task in the name column. 515 if ([[tableColumn identifier] intValue] == IDS_TASK_MANAGER_PAGE_COLUMN) { 516 DCHECK([cell isKindOfClass:[NSButtonCell class]]); 517 NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell); 518 NSString* title = [self modelTextForRow:rowIndex 519 column:[[tableColumn identifier] intValue]]; 520 [buttonCell setTitle:title]; 521 [buttonCell setImage: 522 taskManagerObserver_->GetImageForRow(viewToModelMap_[rowIndex])]; 523 [buttonCell setRefusesFirstResponder:YES]; // Don't push in like a button. 524 [buttonCell setHighlightsBy:NSNoCellMask]; 525 } 526 527 return cell; 528 } 529 530 - (void) tableView:(NSTableView*)tableView 531 sortDescriptorsDidChange:(NSArray*)oldDescriptors { 532 NSArray* newDescriptors = [tableView sortDescriptors]; 533 if ([newDescriptors count] < 1) 534 return; 535 536 currentSortDescriptor_.reset([[newDescriptors objectAtIndex:0] retain]); 537 [self reloadData]; // Sorts. 538 } 539 540 @end 541 542 //////////////////////////////////////////////////////////////////////////////// 543 // TaskManagerMac implementation: 544 545 TaskManagerMac::TaskManagerMac(TaskManager* task_manager, 546 bool highlight_background_resources) 547 : task_manager_(task_manager), 548 model_(task_manager->model()), 549 icon_cache_(this), 550 highlight_background_resources_(highlight_background_resources) { 551 window_controller_ = 552 [[TaskManagerWindowController alloc] 553 initWithTaskManagerObserver:this 554 highlightBackgroundResources:highlight_background_resources]; 555 model_->AddObserver(this); 556 } 557 558 // static 559 TaskManagerMac* TaskManagerMac::instance_ = NULL; 560 561 TaskManagerMac::~TaskManagerMac() { 562 if (this == instance_) { 563 // Do not do this when running in unit tests: |StartUpdating()| never got 564 // called in that case. 565 task_manager_->OnWindowClosed(); 566 } 567 model_->RemoveObserver(this); 568 } 569 570 //////////////////////////////////////////////////////////////////////////////// 571 // TaskManagerMac, TaskManagerModelObserver implementation: 572 573 void TaskManagerMac::OnModelChanged() { 574 icon_cache_.OnModelChanged(); 575 [window_controller_ deselectRows]; 576 [window_controller_ reloadData]; 577 } 578 579 void TaskManagerMac::OnItemsChanged(int start, int length) { 580 icon_cache_.OnItemsChanged(start, length); 581 [window_controller_ reloadData]; 582 } 583 584 void TaskManagerMac::OnItemsAdded(int start, int length) { 585 icon_cache_.OnItemsAdded(start, length); 586 [window_controller_ deselectRows]; 587 [window_controller_ reloadData]; 588 } 589 590 void TaskManagerMac::OnItemsRemoved(int start, int length) { 591 icon_cache_.OnItemsRemoved(start, length); 592 [window_controller_ deselectRows]; 593 [window_controller_ reloadData]; 594 } 595 596 NSImage* TaskManagerMac::GetImageForRow(int row) { 597 return icon_cache_.GetImageForRow(row); 598 } 599 600 //////////////////////////////////////////////////////////////////////////////// 601 // TaskManagerMac, public: 602 603 void TaskManagerMac::WindowWasClosed() { 604 delete this; 605 instance_ = NULL; 606 } 607 608 int TaskManagerMac::RowCount() const { 609 return model_->ResourceCount(); 610 } 611 612 SkBitmap TaskManagerMac::GetIcon(int r) const { 613 return model_->GetResourceIcon(r); 614 } 615 616 bool TaskManagerMac::IsBackgroundRow(int row) const { 617 return model_->IsBackgroundResource(row); 618 } 619 620 // static 621 void TaskManagerMac::Show(bool highlight_background_resources) { 622 if (instance_) { 623 if (instance_->highlight_background_resources_ == 624 highlight_background_resources) { 625 // There's a Task manager window open already, so just activate it. 626 [[instance_->window_controller_ window] 627 makeKeyAndOrderFront:instance_->window_controller_]; 628 return; 629 } else { 630 // The user is switching between "View Background Pages" and 631 // "Task Manager" so close the existing window and fall through to 632 // open a new one. 633 [[instance_->window_controller_ window] close]; 634 } 635 } 636 // Create a new instance. 637 instance_ = new TaskManagerMac(TaskManager::GetInstance(), 638 highlight_background_resources); 639 instance_->model_->StartUpdating(); 640 } 641