<commit_before>// 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.

#include "chrome/browser/cocoa/task_manager_mac.h"

#include <algorithm>
#include <vector>

#include "app/l10n_util_mac.h"
#include "base/mac_util.h"
#include "base/sys_string_conversions.h"
#include "chrome/browser/browser_process.h"
#import "chrome/browser/cocoa/window_size_autosaver.h"
#include "chrome/common/pref_names.h"
#include "grit/generated_resources.h"
#include "third_party/skia/include/core/SkBitmap.h"

namespace {

// Width of "a" and most other letters/digits in "small" table views.
const int kCharWidth = 6;

// Some of the strings below have spaces at the end or are missing letters, to
// make the columns look nicer, and to take potentially longer localized strings
// into account.
const struct ColumnWidth {
  int columnId;
  int minWidth;
  int maxWidth;  // If this is -1, 1.5*minColumWidth is used as max width.
} columnWidths[] = {
  // Note that arraysize includes the trailing \0. That's intended.
  { IDS_TASK_MANAGER_PAGE_COLUMN, 120, 600 },
  { IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN,
      arraysize("800 MiB") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_SHARED_MEM_COLUMN,
      arraysize("800 MiB") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN,
      arraysize("800 MiB") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_CPU_COLUMN,
      arraysize("99.9") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_NET_COLUMN,
      arraysize("150 kiB/s") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_PROCESS_ID_COLUMN,
      arraysize("73099  ") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN,
      arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN,
      arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN,
      arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN,
      arraysize("800 kB") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN,
      arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN,
      arraysize("15 ") * kCharWidth, -1 },
};

class SortHelper {
 public:
  SortHelper(TaskManagerModel* model, NSSortDescriptor* column)
      : sort_column_([[column key] intValue]),
        ascending_([column ascending]),
        model_(model) {}

  bool operator()(int a, int b) {
    std::pair<int, int> group_range1 = model_->GetGroupRangeForResource(a);
    std::pair<int, int> group_range2 = model_->GetGroupRangeForResource(b);
    if (group_range1 == group_range2) {
      // The two rows are in the same group, sort so that items in the same
      // group always appear in the same order. |ascending_| is intentionally
      // ignored.
      return a < b;
    }
    // Sort by the first entry of each of the groups.
    int cmp_result = model_->CompareValues(
        group_range1.first, group_range2.first, sort_column_);
    if (!ascending_)
      cmp_result = -cmp_result;
    return cmp_result < 0;
  }
 private:
  int sort_column_;
  bool ascending_;
  TaskManagerModel* model_;  // weak;
};

}  // namespace

@interface TaskManagerWindowController (Private)
- (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible;
- (void)setUpTableColumns;
- (void)setUpTableHeaderContextMenu;
- (void)toggleColumn:(id)sender;
- (void)adjustSelectionAndEndProcessButton;
- (void)deselectRows;
@end

////////////////////////////////////////////////////////////////////////////////
// TaskManagerWindowController implementation:

@implementation TaskManagerWindowController

- (id)initWithTaskManagerObserver:(TaskManagerMac*)taskManagerObserver {
  NSString* nibpath = [mac_util::MainAppBundle()
                        pathForResource:@"TaskManager"
                                 ofType:@"nib"];
  if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
    taskManagerObserver_ = taskManagerObserver;
    taskManager_ = taskManagerObserver_->task_manager();
    model_ = taskManager_->model();

    if (g_browser_process && g_browser_process->local_state()) {
      size_saver_.reset([[WindowSizeAutosaver alloc]
          initWithWindow:[self window]
             prefService:g_browser_process->local_state()
                    path:prefs::kTaskManagerWindowPlacement]);
    }
    [self showWindow:self];
  }
  return self;
}

- (void)sortShuffleArray {
  viewToModelMap_.resize(model_->ResourceCount());
  for (size_t i = 0; i < viewToModelMap_.size(); ++i)
    viewToModelMap_[i] = i;

  std::sort(viewToModelMap_.begin(), viewToModelMap_.end(),
            SortHelper(model_, currentSortDescriptor_.get()));

  modelToViewMap_.resize(viewToModelMap_.size());
  for (size_t i = 0; i < viewToModelMap_.size(); ++i)
    modelToViewMap_[viewToModelMap_[i]] = i;
}

- (void)reloadData {
  // Store old view indices, and the model indices they map to.
  NSIndexSet* viewSelection = [tableView_ selectedRowIndexes];
  std::vector<int> modelSelection;
  for (NSUInteger i = [viewSelection lastIndex];
       i != NSNotFound;
       i = [viewSelection indexLessThanIndex:i]) {
    modelSelection.push_back(viewToModelMap_[i]);
  }

  // Sort.
  [self sortShuffleArray];

  // Use the model indices to get the new view indices of the selection, and
  // set selection to that. This assumes that no rows were added or removed
  // (in that case, the selection is cleared before -reloadData is called).
  if (modelSelection.size() > 0)
    DCHECK_EQ([tableView_ numberOfRows], model_->ResourceCount());
  NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
  for (size_t i = 0; i < modelSelection.size(); ++i)
    [indexSet addIndex:modelToViewMap_[modelSelection[i]]];
  [tableView_ selectRowIndexes:indexSet byExtendingSelection:NO];

  [tableView_ reloadData];
  [self adjustSelectionAndEndProcessButton];
}

- (IBAction)statsLinkClicked:(id)sender {
  TaskManager::GetInstance()->OpenAboutMemory();
}

- (IBAction)killSelectedProcesses:(id)sender {
  NSIndexSet* selection = [tableView_ selectedRowIndexes];
  for (NSUInteger i = [selection lastIndex];
       i != NSNotFound;
       i = [selection indexLessThanIndex:i]) {
    taskManager_->KillProcess(viewToModelMap_[i]);
  }
}

- (void)selectDoubleClickedTab:(id)sender {
  NSInteger row = [tableView_ clickedRow];
  if (row < 0)
    return;  // Happens e.g. if the table header is double-clicked.
  taskManager_->ActivateProcess(viewToModelMap_[row]);
}

- (NSTableView*)tableView {
  return tableView_;
}

- (void)awakeFromNib {
  [self setUpTableColumns];
  [self setUpTableHeaderContextMenu];
  [self adjustSelectionAndEndProcessButton];

  [tableView_ setDoubleAction:@selector(selectDoubleClickedTab:)];
  [tableView_ sizeToFit];
}

// Adds a column which has the given string id as title. |isVisible| specifies
// if the column is initially visible.
- (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible {
  scoped_nsobject<NSTableColumn> column([[NSTableColumn alloc]
      initWithIdentifier:[NSNumber numberWithInt:columnId]]);

  NSTextAlignment textAlignment = columnId == IDS_TASK_MANAGER_PAGE_COLUMN ?
      NSLeftTextAlignment : NSRightTextAlignment;

  [[column.get() headerCell]
      setStringValue:l10n_util::GetNSStringWithFixup(columnId)];
  [[column.get() headerCell] setAlignment:textAlignment];
  [[column.get() dataCell] setAlignment:textAlignment];

  NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
  [[column.get() dataCell] setFont:font];

  [column.get() setHidden:!isVisible];
  [column.get() setEditable:NO];

  // The page column should by default be sorted ascending.<commit_msg>Prevent a crash in TaskManagerWindowController by nil-ing the dataSource and delegate in dealloc.<commit_after>// 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.

#include "chrome/browser/cocoa/task_manager_mac.h"

#include <algorithm>
#include <vector>

#include "app/l10n_util_mac.h"
#include "base/mac_util.h"
#include "base/sys_string_conversions.h"
#include "chrome/browser/browser_process.h"
#import "chrome/browser/cocoa/window_size_autosaver.h"
#include "chrome/common/pref_names.h"
#include "grit/generated_resources.h"
#include "third_party/skia/include/core/SkBitmap.h"

namespace {

// Width of "a" and most other letters/digits in "small" table views.
const int kCharWidth = 6;

// Some of the strings below have spaces at the end or are missing letters, to
// make the columns look nicer, and to take potentially longer localized strings
// into account.
const struct ColumnWidth {
  int columnId;
  int minWidth;
  int maxWidth;  // If this is -1, 1.5*minColumWidth is used as max width.
} columnWidths[] = {
  // Note that arraysize includes the trailing \0. That's intended.
  { IDS_TASK_MANAGER_PAGE_COLUMN, 120, 600 },
  { IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN,
      arraysize("800 MiB") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_SHARED_MEM_COLUMN,
      arraysize("800 MiB") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN,
      arraysize("800 MiB") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_CPU_COLUMN,
      arraysize("99.9") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_NET_COLUMN,
      arraysize("150 kiB/s") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_PROCESS_ID_COLUMN,
      arraysize("73099  ") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN,
      arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN,
      arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN,
      arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN,
      arraysize("800 kB") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN,
      arraysize("2000.0K (2000.0 live)") * kCharWidth, -1 },
  { IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN,
      arraysize("15 ") * kCharWidth, -1 },
};

class SortHelper {
 public:
  SortHelper(TaskManagerModel* model, NSSortDescriptor* column)
      : sort_column_([[column key] intValue]),
        ascending_([column ascending]),
        model_(model) {}

  bool operator()(int a, int b) {
    std::pair<int, int> group_range1 = model_->GetGroupRangeForResource(a);
    std::pair<int, int> group_range2 = model_->GetGroupRangeForResource(b);
    if (group_range1 == group_range2) {
      // The two rows are in the same group, sort so that items in the same
      // group always appear in the same order. |ascending_| is intentionally
      // ignored.
      return a < b;
    }
    // Sort by the first entry of each of the groups.
    int cmp_result = model_->CompareValues(
        group_range1.first, group_range2.first, sort_column_);
    if (!ascending_)
      cmp_result = -cmp_result;
    return cmp_result < 0;
  }
 private:
  int sort_column_;
  bool ascending_;
  TaskManagerModel* model_;  // weak;
};

}  // namespace

@interface TaskManagerWindowController (Private)
- (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible;
- (void)setUpTableColumns;
- (void)setUpTableHeaderContextMenu;
- (void)toggleColumn:(id)sender;
- (void)adjustSelectionAndEndProcessButton;
- (void)deselectRows;
@end

////////////////////////////////////////////////////////////////////////////////
// TaskManagerWindowController implementation:

@implementation TaskManagerWindowController

- (id)initWithTaskManagerObserver:(TaskManagerMac*)taskManagerObserver {
  NSString* nibpath = [mac_util::MainAppBundle()
                        pathForResource:@"TaskManager"
                                 ofType:@"nib"];
  if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
    taskManagerObserver_ = taskManagerObserver;
    taskManager_ = taskManagerObserver_->task_manager();
    model_ = taskManager_->model();

    if (g_browser_process && g_browser_process->local_state()) {
      size_saver_.reset([[WindowSizeAutosaver alloc]
          initWithWindow:[self window]
             prefService:g_browser_process->local_state()
                    path:prefs::kTaskManagerWindowPlacement]);
    }
    [self showWindow:self];
  }
  return self;
}

- (void)sortShuffleArray {
  viewToModelMap_.resize(model_->ResourceCount());
  for (size_t i = 0; i < viewToModelMap_.size(); ++i)
    viewToModelMap_[i] = i;

  std::sort(viewToModelMap_.begin(), viewToModelMap_.end(),
            SortHelper(model_, currentSortDescriptor_.get()));

  modelToViewMap_.resize(viewToModelMap_.size());
  for (size_t i = 0; i < viewToModelMap_.size(); ++i)
    modelToViewMap_[viewToModelMap_[i]] = i;
}

- (void)reloadData {
  // Store old view indices, and the model indices they map to.
  NSIndexSet* viewSelection = [tableView_ selectedRowIndexes];
  std::vector<int> modelSelection;
  for (NSUInteger i = [viewSelection lastIndex];
       i != NSNotFound;
       i = [viewSelection indexLessThanIndex:i]) {
    modelSelection.push_back(viewToModelMap_[i]);
  }

  // Sort.
  [self sortShuffleArray];

  // Use the model indices to get the new view indices of the selection, and
  // set selection to that. This assumes that no rows were added or removed
  // (in that case, the selection is cleared before -reloadData is called).
  if (modelSelection.size() > 0)
    DCHECK_EQ([tableView_ numberOfRows], model_->ResourceCount());
  NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
  for (size_t i = 0; i < modelSelection.size(); ++i)
    [indexSet addIndex:modelToViewMap_[modelSelection[i]]];
  [tableView_ selectRowIndexes:indexSet byExtendingSelection:NO];

  [tableView_ reloadData];
  [self adjustSelectionAndEndProcessButton];
}

- (IBAction)statsLinkClicked:(id)sender {
  TaskManager::GetInstance()->OpenAboutMemory();
}

- (IBAction)killSelectedProcesses:(id)sender {
  NSIndexSet* selection = [tableView_ selectedRowIndexes];
  for (NSUInteger i = [selection lastIndex];
       i != NSNotFound;
       i = [selection indexLessThanIndex:i]) {
    taskManager_->KillProcess(viewToModelMap_[i]);
  }
}

- (void)selectDoubleClickedTab:(id)sender {
  NSInteger row = [tableView_ clickedRow];
  if (row < 0)
    return;  // Happens e.g. if the table header is double-clicked.
  taskManager_->ActivateProcess(viewToModelMap_[row]);
}

- (NSTableView*)tableView {
  return tableView_;
}

- (void)awakeFromNib {
  [self setUpTableColumns];
  [self setUpTableHeaderContextMenu];
  [self adjustSelectionAndEndProcessButton];

  [tableView_ setDoubleAction:@selector(selectDoubleClickedTab:)];
  [tableView_ sizeToFit];
}

- (void)dealloc {
  [tableView_ setDelegate:nil];
  [tableView_ setDataSource:nil];
  [super dealloc];
}

// Adds a column which has the given string id as title. |isVisible| specifies
// if the column is initially visible.
- (NSTableColumn*)addColumnWithId:(int)columnId visible:(BOOL)isVisible {
  scoped_nsobject<NSTableColumn> column([[NSTableColumn alloc]
      initWithIdentifier:[NSNumber numberWithInt:columnId]]);

  NSTextAlignment textAlignment = columnId == IDS_TASK_MANAGER_PAGE_COLUMN ?
      NSLeftTextAlignment : NSRightTextAlignment;

  [[column.get() headerCell]
      setStringValue:l10n_util::GetNSStringWithFixup(columnId)];
  [[column.get() headerCell] setAlignment:textAlignment];
  [[column.get() dataCell] setAlignment:textAlignment];

  NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
  [[column.get() dataCell] setFont:font];

  [column.get() setHidden:!isVisible];
  [column.get() setEditable:NO];

  // The page column should by default be sorted ascending.
{'fraction_non_alphanumeric': 0.12582278481012657, 'fraction_numerical': 0.013670886075949367, 'mean_word_length': 4.1020342266709715, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6600091', 'n_tokens_mistral': 5346, 'n_tokens_neox': 5135, 'n_words': 1132}