// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/ui/cocoa/task_manager_mac.h"
#include <stddef.h>
#include <algorithm>
#include <vector>
#include "base/apple/bundle_locations.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/sys_string_conversions.h"
#include "build/buildflag.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/task_manager/task_manager_interface.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/cocoa/task_manager_mac_table_view.h"
#import "chrome/browser/ui/cocoa/window_size_autosaver.h"
#include "chrome/browser/ui/task_manager/task_manager_columns.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_util_mac.h"
namespace {
NSString* ColumnIdentifier(int id) {
return [NSString stringWithFormat:@"%d", id];
}
} // namespace
@interface TaskManagerWindowController ()
@property(readonly) std::vector<size_t> modelSelection;
- (void)sortShuffleArray;
- (void)reloadDataWithModelSelection:(std::vector<size_t>)modelSelection;
- (void)reloadDataWithRowsAdded:(size_t)addedRows
addedAtIndex:(size_t)addedRowIndex;
- (void)reloadDataWithRowsRemoved:(size_t)removedRows
removedAtIndex:(size_t)removedRowIndex;
- (NSWindow*)createAndLayOutWindow;
- (NSTableColumn*)addColumnWithData:
(const task_manager::TableColumnData&)columnData;
- (void)setUpTableColumns;
- (void)setUpTableHeaderContextMenu;
- (void)toggleColumn:(NSMenuItem*)item;
- (void)adjustSelectionAndEndProcessButton;
- (void)deselectRows;
@end
////////////////////////////////////////////////////////////////////////////////
// TaskManagerWindowController implementation:
@implementation TaskManagerWindowController {
NSTableView* __strong _tableView;
NSButton* __strong _endProcessButton;
raw_ptr<task_manager::TaskManagerMac, DanglingUntriaged>
_taskManagerMac; // weak
raw_ptr<task_manager::TaskManagerTableModel, DanglingUntriaged>
_tableModel; // weak
WindowSizeAutosaver* __strong _size_saver;
// These contain a permutation of [0..|tableModel_->RowCount() - 1|]. Used to
// implement sorting.
std::vector<size_t> _viewToModelMap;
std::vector<size_t> _modelToViewMap;
// Descriptor of the current sort column.
task_manager::TableSortDescriptor _currentSortDescriptor;
// Re-entrancy flag to allow meddling with the sort descriptor.
BOOL _withinSortDescriptorsDidChange;
}
- (instancetype)
initWithTaskManagerMac:(task_manager::TaskManagerMac*)taskManagerMac
tableModel:(task_manager::TaskManagerTableModel*)tableModel {
NSWindow* window = [self createAndLayOutWindow];
if ((self = [super initWithWindow:window])) {
_taskManagerMac = taskManagerMac;
_tableModel = tableModel;
window.delegate = self;
_tableView.delegate = self;
_tableView.dataSource = self;
if (g_browser_process && g_browser_process->local_state()) {
_size_saver = [[WindowSizeAutosaver alloc]
initWithWindow:self.window
prefService:g_browser_process->local_state()
path:prefs::kTaskManagerWindowPlacement];
}
self.window.excludedFromWindowsMenu = YES;
[self setUpTableColumns];
[self setUpTableHeaderContextMenu];
[self adjustSelectionAndEndProcessButton];
[_tableView sizeToFit];
[self reloadData];
[self showWindow:self];
}
return self;
}
- (void)sortShuffleArray {
_viewToModelMap.resize(_tableModel->RowCount());
for (size_t i = 0; i < _viewToModelMap.size(); ++i)
_viewToModelMap[i] = i;
if (_currentSortDescriptor.sorted_column_id != -1) {
task_manager::TaskManagerTableModel* tableModel = _tableModel;
task_manager::TableSortDescriptor currentSortDescriptor =
_currentSortDescriptor;
std::stable_sort(_viewToModelMap.begin(), _viewToModelMap.end(),
[tableModel, currentSortDescriptor](int a, int b) {
size_t aStart, aLength;
tableModel->GetRowsGroupRange(a, &aStart, &aLength);
size_t bStart, bLength;
tableModel->GetRowsGroupRange(b, &bStart, &bLength);
if (aStart == bStart) {
// The two rows are in the same group, sort so that
// items in the same group always appear in the same
// order. The sort descriptor's ascending value is
// intentionally ignored.
return a < b;
}
// Sort by the first entry of each of the groups.
int cmp_result = tableModel->CompareValues(
aStart, bStart,
currentSortDescriptor.sorted_column_id);
if (!currentSortDescriptor.is_ascending)
cmp_result = -cmp_result;
return cmp_result < 0;
});
}
_modelToViewMap.resize(_viewToModelMap.size());
for (size_t i = 0; i < _viewToModelMap.size(); ++i)
_modelToViewMap[_viewToModelMap[i]] = i;
}
- (void)reloadData {
[self reloadDataWithRowsAdded:0 addedAtIndex:0];
}
- (std::vector<size_t>)modelSelection {
NSIndexSet* viewSelection = _tableView.selectedRowIndexes;
std::vector<size_t> modelSelection;
for (NSUInteger i = viewSelection.lastIndex; i != NSNotFound;
i = [viewSelection indexLessThanIndex:i]) {
modelSelection.push_back(_viewToModelMap[i]);
}
return modelSelection;
}
- (void)reloadDataWithModelSelection:(std::vector<size_t>)modelSelection {
// Sort.
[self sortShuffleArray];
// Reload the data.
[_tableView reloadData];
// Reload the selection.
NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
for (auto selectedItem : modelSelection)
[indexSet addIndex:_modelToViewMap[selectedItem]];
[_tableView selectRowIndexes:indexSet byExtendingSelection:NO];
[self adjustSelectionAndEndProcessButton];
}
- (void)reloadDataWithRowsAdded:(size_t)addedRows
addedAtIndex:(size_t)addedRowIndex {
std::vector<size_t> modelSelection = self.modelSelection;
// Adjust for any added rows.
for (size_t& selectedItem : modelSelection) {
if (selectedItem >= addedRowIndex)
selectedItem += addedRows;
}
[self reloadDataWithModelSelection:std::move(modelSelection)];
}
- (void)reloadDataWithRowsRemoved:(size_t)removedRows
removedAtIndex:(size_t)removedRowIndex {
std::vector<size_t> modelSelection = self.modelSelection;
// Adjust for any removed rows.
std::vector<size_t> newModelSelection;
for (size_t selectedItem : modelSelection) {
if (selectedItem < removedRowIndex)
newModelSelection.push_back(selectedItem);
else if (selectedItem >= removedRowIndex + removedRows)
newModelSelection.push_back(selectedItem - removedRows);
}
[self reloadDataWithModelSelection:std::move(newModelSelection)];
}
- (task_manager::TableSortDescriptor)sortDescriptor {
return _currentSortDescriptor;
}
- (void)setSortDescriptor:(task_manager::TableSortDescriptor)sortDescriptor {
NSSortDescriptor* nsSortDescriptor = [[NSSortDescriptor alloc]
initWithKey:ColumnIdentifier(sortDescriptor.sorted_column_id)
ascending:sortDescriptor.is_ascending];
[_tableView setSortDescriptors:@[ nsSortDescriptor ]];
}
- (BOOL)visibilityOfColumnWithId:(int)columnId {
NSTableColumn* column =
[_tableView tableColumnWithIdentifier:ColumnIdentifier(columnId)];
return column ? !column.hidden : NO;
}
- (void)setVisibility:(BOOL)visibility ofColumnWithId:(int)columnId {
NSTableColumn* column =
[_tableView tableColumnWithIdentifier:ColumnIdentifier(columnId)];
column.hidden = !visibility;
[_tableView sizeToFit];
[_tableView setNeedsDisplay:YES];
}
- (IBAction)killSelectedProcesses:(id)sender {
NSIndexSet* selection = _tableView.selectedRowIndexes;
for (NSUInteger i = selection.lastIndex; i != NSNotFound;
i = [selection indexLessThanIndex:i]) {
_tableModel->KillTask(_viewToModelMap[i]);
}
}
- (void)tableWasDoubleClicked:(id)sender {
NSInteger row = _tableView.clickedRow;
if (row < 0)
return; // Happens e.g. if the table header is double-clicked.
_tableModel->ActivateTask(_viewToModelMap[row]);
}
- (void)dealloc {
// Paranoia. These should have been nilled out in -windowWillClose: but let's
// make sure we have no dangling references.
_tableView.delegate = nil;
_tableView.dataSource = nil;
}
// Creates a NSWindow for the task manager and lays out the views inside the
// content view.
- (NSWindow*)createAndLayOutWindow {
static constexpr CGFloat kWindowWidth = 480;
static constexpr CGFloat kMargin = 20;
NSWindow* window = [[NSWindow alloc]
initWithContentRect:NSMakeRect(195, 240, kWindowWidth, 270)
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskResizable
backing:NSBackingStoreBuffered
defer:YES];
window.minSize = NSMakeSize(300, 200);
window.title = l10n_util::GetNSString(IDS_TASK_MANAGER_TITLE);
NSView* contentView = window.contentView;
// Create the button that terminates the selected process in the table.
_endProcessButton =
[NSButton buttonWithTitle:l10n_util::GetNSString(IDS_TASK_MANAGER_KILL)
target:self
action:@selector(killSelectedProcesses:)];
_endProcessButton.autoresizingMask = NSViewMinXMargin | NSViewMaxYMargin;
[_endProcessButton sizeToFit];
NSRect buttonFrame = _endProcessButton.frame;
buttonFrame.size.width += kMargin;
// Adjust the button's origin so that it is flush with the right-hand side of
// the table.
buttonFrame.origin.x =
NSWidth(contentView.frame) - NSWidth(buttonFrame) - kMargin + 6;
// Use only half the margin, since the full margin is too much whitespace.
buttonFrame.origin.y = kMargin / 2;
_endProcessButton.frame = buttonFrame;
_endProcessButton.keyEquivalent = @"\r";
[contentView addSubview:_endProcessButton];
// Create a scroll view to house the table view.
CGFloat scrollViewY = NSMaxY(buttonFrame) + kMargin / 2;
NSRect scrollViewFrame =
NSMakeRect(kMargin, scrollViewY, kWindowWidth - 2 * kMargin,
NSHeight(window.frame) - 2 * kMargin - scrollViewY);
NSScrollView* scrollView =
[[NSScrollView alloc] initWithFrame:scrollViewFrame];
scrollView.autoresizesSubviews = YES;
scrollView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
scrollView.borderType = NSBezelBorder;
scrollView.focusRingType = NSFocusRingTypeNone;
scrollView.hasVerticalScroller = YES;
scrollView.verticalScroller.controlSize = NSControlSizeSmall;
[contentView addSubview:scrollView];
// Create the table view. The data source and delegate are connected in
// the designated initializer.
TaskManagerMacTableView* tableView = [[TaskManagerMacTableView alloc]
initWithFrame:NSMakeRect(0, 0, 400, 200)];
tableView.allowsColumnReordering = NO;
tableView.allowsMultipleSelection = YES;
// No autosaving, since column identifiers are IDS_ values which are not
// stable. TODO(avi): Would it be worth it to find stable identifiers so that
// we could use autosaving?
tableView.autosaveTableColumns = NO;
tableView.columnAutoresizingStyle = NSTableViewUniformColumnAutoresizingStyle;
tableView.doubleAction = @selector(tableWasDoubleClicked:);
tableView.focusRingType = NSFocusRingTypeNone;
tableView.intercellSpacing = NSMakeSize(0, 0);
tableView.usesAlternatingRowBackgroundColors = YES;
_tableView = tableView;
scrollView.documentView = tableView;
return window;
}
// Adds a column which has the given string id as title. |isVisible| specifies
// if the column is initially visible.
- (NSTableColumn*)addColumnWithData:
(const task_manager::TableColumnData&)columnData {
NSTableColumn* column = [[NSTableColumn alloc]
initWithIdentifier:ColumnIdentifier(columnData.id)];
NSTableHeaderCell* headerCell = column.headerCell;
NSCell* dataCell = column.dataCell;
NSTextAlignment textAlignment;
// There are no "leading" and "trailing" constants in `NSTextAlignment` so do
// it manually.
if (NSApp.userInterfaceLayoutDirection ==
NSUserInterfaceLayoutDirectionRightToLeft) {
textAlignment = (columnData.align == ui::TableColumn::LEFT)
? NSTextAlignmentRight
: NSTextAlignmentLeft;
} else {
textAlignment = (columnData.align == ui::TableColumn::LEFT)
? NSTextAlignmentLeft
: NSTextAlignmentRight;
}
headerCell.alignment = textAlignment;
dataCell.alignment = textAlignment;
headerCell.stringValue = l10n_util::GetNSStringWithFixup(columnData.id);
dataCell.font =
[NSFont monospacedDigitSystemFontOfSize:NSFont.smallSystemFontSize
weight:NSFontWeightRegular];
column.hidden = !columnData.default_visibility;
column.editable = NO;
NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:ColumnIdentifier(columnData.id)
ascending:columnData.initial_sort_is_ascending];
[column setSortDescriptorPrototype:sortDescriptor];
column.minWidth = columnData.min_width;
// If there is no specified max width, use a reasonable value of 1.5x the min
// width, but make sure that the max width is big enough to actually show the
// entire column title.
const int kTitleMargin = 40; // Space for the arrow, etc.
int maxWidth = columnData.max_width;
if (maxWidth <= 0) {
maxWidth = 3 * columnData.min_width / 2;
}
int columnTitleWidth =
[headerCell.stringValue
sizeWithAttributes:@{NSFontAttributeName : headerCell.font}]
.width +
kTitleMargin;
maxWidth = std::max(maxWidth, columnTitleWidth);
column.maxWidth = maxWidth;
column.resizingMask =
NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask;
[_tableView addTableColumn:column];
return column;
}
// Adds all the task manager's columns to the table.
- (void)setUpTableColumns {
for (NSTableColumn* column in _tableView.tableColumns) {
[_tableView removeTableColumn:column];
}
for (size_t i = 0; i < task_manager::kColumnsSize; ++i) {
const auto& columnData = task_manager::kColumns[i];
NSTableColumn* column = [self addColumnWithData:columnData];
if (columnData.id == IDS_TASK_MANAGER_TASK_COLUMN) {
// The task column displays an icon for every row, done by an
// NSButtonCell.
NSCell* currentCell = column.dataCell;
NSButtonCell* nameCell = [[NSButtonCell alloc] initTextCell:@""];
nameCell.imagePosition = NSImageLeft;
nameCell.buttonType = NSButtonTypeSwitch;
nameCell.alignment = currentCell.alignment;
nameCell.font = currentCell.font;
column.dataCell = nameCell;
}
}
}
// Creates a context menu for the table header that allows the user to toggle
// which columns should be shown and which should be hidden (like the Activity
// Monitor.app's table header context menu).
- (void)setUpTableHeaderContextMenu {
NSMenu* contextMenu =
[[NSMenu alloc] initWithTitle:@"Task Manager context menu"];
contextMenu.delegate = self;
_tableView.headerView.menu = contextMenu;
}
- (void)menuNeedsUpdate:(NSMenu*)menu {
[menu removeAllItems];
for (NSTableColumn* column in _tableView.tableColumns) {
NSMenuItem* item = [menu addItemWithTitle:column.headerCell.stringValue
action:@selector(toggleColumn:)
keyEquivalent:@""];
item.target = self;
item.representedObject = column;
item.state = column.hidden ? NSControlStateValueOff : NSControlStateValueOn;
}
}
// Callback for the table header context menu. Toggles visibility of the table
// column associated with the clicked menu item.
- (void)toggleColumn:(NSMenuItem*)item {
CHECK([item isKindOfClass:[NSMenuItem class]]);
if (![item isKindOfClass:[NSMenuItem class]])
return;
NSTableColumn* column = item.representedObject;
int columnId = column.identifier.intValue;
CHECK(column);
NSInteger oldState = item.state;
NSInteger newState = oldState == NSControlStateValueOn
? NSControlStateValueOff
: NSControlStateValueOn;
// If hiding the column, make sure at least one column will remain visible.
if (newState == NSControlStateValueOff) {
// Find the first column that will be visible after hiding |column|.
NSTableColumn* firstRemainingVisibleColumn = nil;
for (NSTableColumn* nextColumn in _tableView.tableColumns) {
if (nextColumn != column && !nextColumn.hidden) {
firstRemainingVisibleColumn = nextColumn;
break;
}
}
// If no column will be visible, abort the toggle. This will basically cause
// the toggle operation to silently fail. The other way to ensure at least
// one visible column is to disable the menu item corresponding to the last
// remaining visible column. That would place the menu in a weird state to
// the user, where there's one item somewhere that's grayed out with no
// clear explanation of why. It will be rare for a user to try hiding all
// columns, but we still want to guard against it. If they are really intent
// on hiding the last visible column (perhaps they plan to choose another
// one after that to be visible), odds are they will try making another
// column visible and then hiding the one column that would not hide.
if (firstRemainingVisibleColumn == nil) {
return;
}
// If |column| is being used to sort the table (i.e. it's the primary sort
// column), make the first remaining visible column the new primary sort
// column.
int primarySortColumnId = _currentSortDescriptor.sorted_column_id;
DCHECK(primarySortColumnId);
if (primarySortColumnId == columnId) {
NSSortDescriptor* newSortDescriptor =
[firstRemainingVisibleColumn sortDescriptorPrototype];
[_tableView setSortDescriptors:@[ newSortDescriptor ]];
}
}
// Make the change. (This will call back into the SetColumnVisibility()
// function to actually do the visibility change.)
_tableModel->ToggleColumnVisibility(columnId);
}
// This function appropriately sets the enabled states on the table's editing
// buttons.
- (void)adjustSelectionAndEndProcessButton {
bool allSelectionRowsAreKillableTasks = true;
NSMutableIndexSet* groupIndexes = [NSMutableIndexSet indexSet];
NSIndexSet* selection = _tableView.selectedRowIndexes;
for (NSUInteger i = selection.lastIndex; i != NSNotFound;
i = [selection indexLessThanIndex:i]) {
int modelIndex = _viewToModelMap[i];
if (!_tableModel->IsTaskKillable(modelIndex))
allSelectionRowsAreKillableTasks = false;
size_t groupStart, groupLength;
_tableModel->GetRowsGroupRange(modelIndex, &groupStart, &groupLength);
for (size_t j = 0; j < groupLength; ++j)
[groupIndexes addIndex:_modelToViewMap[groupStart + j]];
}
[_tableView selectRowIndexes:groupIndexes byExtendingSelection:YES];
bool enabled = selection.count > 0 && allSelectionRowsAreKillableTasks &&
task_manager::TaskManagerInterface::IsEndProcessEnabled();
[_endProcessButton setEnabled:enabled];
}
- (void)deselectRows {
[_tableView deselectAll:self];
}
// Table view delegate methods.
// The selection is being changed by mouse (drag/click).
- (void)tableViewSelectionIsChanging:(NSNotification*)aNotification {
[self adjustSelectionAndEndProcessButton];
}
// The selection is being changed by keyboard (arrows).
- (void)tableViewSelectionDidChange:(NSNotification*)aNotification {
[self adjustSelectionAndEndProcessButton];
}
- (void)windowWillClose:(NSNotification*)notification {
if (_taskManagerMac) {
_tableModel->StoreColumnsSettings();
_tableModel = nullptr;
// Now that there is no model, ensure that this object gets no data requests
// in the window of time between the release and the actual dealloc.
// https://crbug.com/763367
_tableView.delegate = nil;
_tableView.dataSource = nil;
// The _taskManagerMac holds the owning reference to this object, so notify
// it about the closure last.
auto localTaskManagerMac = _taskManagerMac;
_taskManagerMac = nullptr;
localTaskManagerMac->WindowWasClosed();
// Do nothing else after this.
}
}
@end
@implementation TaskManagerWindowController (NSTableDataSource)
- (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
DCHECK(tableView == _tableView || _tableView == nil);
return _tableModel->RowCount();
}
- (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row {
return 16;
}
- (NSString*)modelTextForRow:(int)row column:(int)columnId {
DCHECK_LT(static_cast<size_t>(row), _viewToModelMap.size());
return base::SysUTF16ToNSString(
_tableModel->GetText(_viewToModelMap[row], columnId));
}
- (id)tableView:(NSTableView*)tableView
objectValueForTableColumn:(NSTableColumn*)tableColumn
row:(NSInteger)rowIndex {
// NSButtonCells expect an on/off state as objectValue. Their title is set
// in |tableView:dataCellForTableColumn:row:| below.
if (tableColumn.identifier.intValue == IDS_TASK_MANAGER_TASK_COLUMN) {
return [NSNumber numberWithInt:NSControlStateValueOff];
}
return [self modelTextForRow:rowIndex column:tableColumn.identifier.intValue];
}
- (NSCell*)tableView:(NSTableView*)tableView
dataCellForTableColumn:(NSTableColumn*)tableColumn
row:(NSInteger)rowIndex {
NSCell* cell = [tableColumn dataCellForRow:rowIndex];
// Set the favicon and title for the task in the name column.
if (tableColumn.identifier.intValue == IDS_TASK_MANAGER_TASK_COLUMN) {
DCHECK([cell isKindOfClass:[NSButtonCell class]]);
NSButtonCell* buttonCell = static_cast<NSButtonCell*>(cell);
NSString* title = [self modelTextForRow:rowIndex
column:tableColumn.identifier.intValue];
NSColor* textColor = [tableView isRowSelected:rowIndex]
? NSColor.alternateSelectedControlTextColor
: NSColor.labelColor;
NSAttributedString* attributedTitle = [[NSAttributedString alloc]
initWithString:title
attributes:@{
NSForegroundColorAttributeName : textColor,
NSFontAttributeName : cell.font
}];
buttonCell.attributedTitle = attributedTitle;
buttonCell.image =
_taskManagerMac->GetImageForRow(_viewToModelMap[rowIndex]);
buttonCell.refusesFirstResponder = YES; // Don't push in like a button.
buttonCell.highlightsBy = NSNoCellMask;
}
return cell;
}
- (void)tableView:(NSTableView*)tableView
sortDescriptorsDidChange:(NSArray*)oldDescriptors {
if (_withinSortDescriptorsDidChange)
return;
NSSortDescriptor* oldDescriptor = oldDescriptors.firstObject;
NSSortDescriptor* newDescriptor = tableView.sortDescriptors.firstObject;
// Implement three-way sorting, toggling "unsorted" as a third option.
if (oldDescriptor && newDescriptor &&
[oldDescriptor.key isEqual:newDescriptor.key]) {
// The user clicked to change the sort on the previously sorted column.
// AppKit toggled the sort order. However, if the sort was toggled to become
// the initial sorting direction, clear it instead.
NSTableColumn* column = [tableView
tableColumnWithIdentifier:ColumnIdentifier(newDescriptor.key.intValue)];
NSSortDescriptor* initialDescriptor = [column sortDescriptorPrototype];
if (newDescriptor.ascending == initialDescriptor.ascending) {
_withinSortDescriptorsDidChange = YES;
[_tableView setSortDescriptors:@[]];
newDescriptor = nil;
_withinSortDescriptorsDidChange = NO;
}
}
if (newDescriptor) {
_currentSortDescriptor.sorted_column_id = newDescriptor.key.intValue;
_currentSortDescriptor.is_ascending = newDescriptor.ascending;
} else {
_currentSortDescriptor.sorted_column_id = -1;
}
[self reloadData]; // Sorts.
}
@end
@implementation TaskManagerWindowController (TestingAPI)
- (NSTableView*)tableViewForTesting {
return _tableView;
}
- (NSButton*)endProcessButtonForTesting {
return _endProcessButton;
}
@end
namespace task_manager {
////////////////////////////////////////////////////////////////////////////////
// TaskManagerMac implementation:
TaskManagerMac::TaskManagerMac()
: table_model_(this),
window_controller_([[TaskManagerWindowController alloc]
initWithTaskManagerMac:this
tableModel:&table_model_]) {
table_model_.SetObserver(this); // Hook up the ui::TableModelObserver.
table_model_.RetrieveSavedColumnsSettingsAndUpdateTable();
on_app_terminating_subscription_ =
browser_shutdown::AddAppTerminatingCallback(base::BindOnce(
&TaskManagerMac::OnAppTerminating, base::Unretained(this)));
}
// static
TaskManagerMac* TaskManagerMac::instance_ = nullptr;
TaskManagerMac::~TaskManagerMac() {
table_model_.SetObserver(nullptr);
}
////////////////////////////////////////////////////////////////////////////////
// ui::TableModelObserver implementation:
void TaskManagerMac::OnModelChanged() {
[window_controller_ deselectRows];
[window_controller_ reloadData];
}
void TaskManagerMac::OnItemsChanged(size_t start, size_t length) {
[window_controller_ reloadData];
}
void TaskManagerMac::OnItemsAdded(size_t start, size_t length) {
[window_controller_ reloadDataWithRowsAdded:length addedAtIndex:start];
}
void TaskManagerMac::OnItemsRemoved(size_t start, size_t length) {
[window_controller_ reloadDataWithRowsRemoved:length removedAtIndex:start];
}
////////////////////////////////////////////////////////////////////////////////
// TableViewDelegate implementation:
bool TaskManagerMac::IsColumnVisible(int column_id) const {
return [window_controller_ visibilityOfColumnWithId:column_id];
}
bool TaskManagerMac::SetColumnVisibility(int column_id, bool new_visibility) {
[window_controller_ setVisibility:new_visibility ofColumnWithId:column_id];
return true;
}
bool TaskManagerMac::IsTableSorted() const {
return window_controller_.sortDescriptor.sorted_column_id != -1;
}
TableSortDescriptor TaskManagerMac::GetSortDescriptor() const {
return window_controller_.sortDescriptor;
}
void TaskManagerMac::SetSortDescriptor(const TableSortDescriptor& descriptor) {
window_controller_.sortDescriptor = descriptor;
}
void TaskManagerMac::MaybeHighlightActiveTask() {}
////////////////////////////////////////////////////////////////////////////////
// Called by the TaskManagerWindowController:
void TaskManagerMac::WindowWasClosed() {
delete this;
instance_ = nullptr; // |instance_| is static
}
NSImage* TaskManagerMac::GetImageForRow(int row) {
const NSSize kImageSize = NSMakeSize(16.0, 16.0);
NSImage* image =
gfx::NSImageFromImageSkia(table_model_.GetIcon(row).Rasterize(nullptr));
if (image) {
image.size = kImageSize;
} else {
image = [[NSImage alloc] initWithSize:kImageSize];
}
return image;
}
void TaskManagerMac::OnAppTerminating() {
Hide();
}
// static
TaskManagerTableModel* TaskManagerMac::Show() {
if (instance_) {
[instance_->window_controller_.window
makeKeyAndOrderFront:instance_->window_controller_];
} else {
instance_ = new TaskManagerMac();
}
return &instance_->table_model_;
}
// static
void TaskManagerMac::Hide() {
if (instance_)
[instance_->window_controller_ close];
}
} // namespace task_manager
namespace chrome {
// Declared in browser_dialogs.h.
task_manager::TaskManagerTableModel* ShowTaskManager(Browser* browser) {
return task_manager::TaskManagerMac::Show();
}
void HideTaskManager() {
task_manager::TaskManagerMac::Hide();
}
} // namespace chrome