1// Copyright (c) 2011 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/task_manager/task_manager.h"
6
7#include "base/command_line.h"
8#include "base/compiler_specific.h"
9#include "base/metrics/stats_table.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/app/chrome_command_ids.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/memory_purger.h"
14#include "chrome/browser/prefs/pref_service.h"
15#include "chrome/browser/prefs/scoped_user_pref_update.h"
16#include "chrome/browser/ui/browser_list.h"
17#include "chrome/browser/ui/browser_window.h"
18#include "chrome/browser/ui/views/browser_dialogs.h"
19#include "chrome/common/chrome_switches.h"
20#include "chrome/common/pref_names.h"
21#include "grit/chromium_strings.h"
22#include "grit/generated_resources.h"
23#include "grit/theme_resources.h"
24#include "ui/base/l10n/l10n_util.h"
25#include "ui/base/models/table_model_observer.h"
26#include "views/accelerator.h"
27#include "views/background.h"
28#include "views/controls/button/native_button.h"
29#include "views/controls/link.h"
30#include "views/controls/menu/menu.h"
31#include "views/controls/table/group_table_view.h"
32#include "views/controls/table/table_view_observer.h"
33#include "views/layout/layout_constants.h"
34#include "views/widget/widget.h"
35#include "views/window/dialog_delegate.h"
36#include "views/window/window.h"
37
38// The task manager window default size.
39static const int kDefaultWidth = 460;
40static const int kDefaultHeight = 270;
41
42// Yellow highlight used when highlighting background resources.
43static const SkColor kBackgroundResourceHighlight =
44    SkColorSetRGB(0xff,0xf1,0xcd);
45
46namespace {
47
48////////////////////////////////////////////////////////////////////////////////
49// TaskManagerTableModel class
50////////////////////////////////////////////////////////////////////////////////
51
52class TaskManagerTableModel : public views::GroupTableModel,
53                              public TaskManagerModelObserver {
54 public:
55  explicit TaskManagerTableModel(TaskManagerModel* model)
56      : model_(model),
57        observer_(NULL) {
58    model_->AddObserver(this);
59  }
60
61  ~TaskManagerTableModel() {
62    model_->RemoveObserver(this);
63  }
64
65  // GroupTableModel.
66  int RowCount() OVERRIDE;
67  string16 GetText(int row, int column) OVERRIDE;
68  SkBitmap GetIcon(int row) OVERRIDE;
69  void GetGroupRangeForItem(int item, views::GroupRange* range) OVERRIDE;
70  void SetObserver(ui::TableModelObserver* observer) OVERRIDE;
71  virtual int CompareValues(int row1, int row2, int column_id) OVERRIDE;
72
73  // TaskManagerModelObserver.
74  virtual void OnModelChanged();
75  virtual void OnItemsChanged(int start, int length);
76  virtual void OnItemsAdded(int start, int length);
77  virtual void OnItemsRemoved(int start, int length);
78
79  // Returns true if resource corresponding to |row| is a background resource.
80  bool IsBackgroundResource(int row);
81
82 private:
83  TaskManagerModel* model_;
84  ui::TableModelObserver* observer_;
85};
86
87int TaskManagerTableModel::RowCount() {
88  return model_->ResourceCount();
89}
90
91string16 TaskManagerTableModel::GetText(int row, int col_id) {
92  switch (col_id) {
93    case IDS_TASK_MANAGER_PAGE_COLUMN:  // Process
94      return model_->GetResourceTitle(row);
95
96    case IDS_TASK_MANAGER_NET_COLUMN:  // Net
97      return model_->GetResourceNetworkUsage(row);
98
99    case IDS_TASK_MANAGER_CPU_COLUMN:  // CPU
100      if (!model_->IsResourceFirstInGroup(row))
101        return string16();
102      return model_->GetResourceCPUUsage(row);
103
104    case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN:  // Memory
105      if (!model_->IsResourceFirstInGroup(row))
106        return string16();
107      return model_->GetResourcePrivateMemory(row);
108
109    case IDS_TASK_MANAGER_SHARED_MEM_COLUMN:  // Memory
110      if (!model_->IsResourceFirstInGroup(row))
111        return string16();
112      return model_->GetResourceSharedMemory(row);
113
114    case IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN:  // Memory
115      if (!model_->IsResourceFirstInGroup(row))
116        return string16();
117      return model_->GetResourcePhysicalMemory(row);
118
119    case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
120      if (!model_->IsResourceFirstInGroup(row))
121        return string16();
122      return model_->GetResourceProcessId(row);
123
124    case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN:  // Goats Teleported!
125      return model_->GetResourceGoatsTeleported(row);
126
127    case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
128      if (!model_->IsResourceFirstInGroup(row))
129        return string16();
130      return model_->GetResourceWebCoreImageCacheSize(row);
131
132    case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
133      if (!model_->IsResourceFirstInGroup(row))
134        return string16();
135      return model_->GetResourceWebCoreScriptsCacheSize(row);
136
137    case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
138      if (!model_->IsResourceFirstInGroup(row))
139        return string16();
140      return model_->GetResourceWebCoreCSSCacheSize(row);
141
142    case IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN:
143      if (!model_->IsResourceFirstInGroup(row))
144        return string16();
145      return model_->GetResourceSqliteMemoryUsed(row);
146
147    case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
148      if (!model_->IsResourceFirstInGroup(row))
149        return string16();
150      return model_->GetResourceV8MemoryAllocatedSize(row);
151
152    default:
153      NOTREACHED();
154      return string16();
155  }
156}
157
158SkBitmap TaskManagerTableModel::GetIcon(int row) {
159  return model_->GetResourceIcon(row);
160}
161
162
163void TaskManagerTableModel::GetGroupRangeForItem(int item,
164                                                 views::GroupRange* range) {
165  std::pair<int, int> range_pair = model_->GetGroupRangeForResource(item);
166  range->start = range_pair.first;
167  range->length = range_pair.second;
168}
169
170void TaskManagerTableModel::SetObserver(ui::TableModelObserver* observer) {
171  observer_ = observer;
172}
173
174int TaskManagerTableModel::CompareValues(int row1, int row2, int column_id) {
175  return model_->CompareValues(row1, row2, column_id);
176}
177
178void TaskManagerTableModel::OnModelChanged() {
179  if (observer_)
180    observer_->OnModelChanged();
181}
182
183void TaskManagerTableModel::OnItemsChanged(int start, int length) {
184  if (observer_)
185    observer_->OnItemsChanged(start, length);
186}
187
188void TaskManagerTableModel::OnItemsAdded(int start, int length) {
189  if (observer_)
190    observer_->OnItemsAdded(start, length);
191  // There's a bug in the Windows ListView where inserting items with groups
192  // enabled puts them in the wrong position, so we will need to rebuild the
193  // list view in this case.
194  // (see: http://connect.microsoft.com/VisualStudio/feedback/details/115345/).
195  //
196  // Turns out, forcing a list view rebuild causes http://crbug.com/69391
197  // because items are added to the ListView one-at-a-time when initially
198  // displaying the TaskManager, resulting in many ListView rebuilds. So we are
199  // no longer forcing a rebuild for now because the current UI doesn't use
200  // groups - if we are going to add groups in the upcoming TaskManager UI
201  // revamp, we'll need to re-enable this call to OnModelChanged() and also add
202  // code to avoid doing multiple rebuilds on startup (maybe just generate a
203  // single OnModelChanged() call after the initial population).
204
205  // OnModelChanged();
206}
207
208void TaskManagerTableModel::OnItemsRemoved(int start, int length) {
209  if (observer_)
210    observer_->OnItemsRemoved(start, length);
211
212  // We may need to change the indentation of some items if the topmost item
213  // in the group was removed, so update the view.
214  OnModelChanged();
215}
216
217bool TaskManagerTableModel::IsBackgroundResource(int row) {
218  return model_->IsBackgroundResource(row);
219}
220
221// Thin wrapper around GroupTableView to enable setting the background
222// resource highlight color.
223class BackgroundColorGroupTableView : public views::GroupTableView {
224 public:
225  BackgroundColorGroupTableView(TaskManagerTableModel* model,
226                                const std::vector<ui::TableColumn>& columns,
227                                bool highlight_background_resources)
228      : views::GroupTableView(model, columns, views::ICON_AND_TEXT,
229                              false, true, true, true),
230        model_(model) {
231    SetCustomColorsEnabled(highlight_background_resources);
232  }
233
234  virtual ~BackgroundColorGroupTableView() {}
235
236 private:
237  virtual bool GetCellColors(int model_row,
238                             int column,
239                             ItemColor* foreground,
240                             ItemColor* background,
241                             LOGFONT* logfont) {
242    if (!model_->IsBackgroundResource(model_row))
243      return false;
244
245    // Render background resources with a yellow highlight.
246    background->color_is_set = true;
247    background->color = kBackgroundResourceHighlight;
248    foreground->color_is_set = false;
249    return true;
250  }
251
252  TaskManagerTableModel* model_;
253};
254
255// The Task manager UI container.
256class TaskManagerView : public views::View,
257                        public views::ButtonListener,
258                        public views::DialogDelegate,
259                        public views::TableViewObserver,
260                        public views::LinkController,
261                        public views::ContextMenuController,
262                        public views::Menu::Delegate {
263 public:
264  explicit TaskManagerView(bool highlight_background_resources);
265  virtual ~TaskManagerView();
266
267  // Shows the Task manager window, or re-activates an existing one. If
268  // |highlight_background_resources| is true, highlights the background
269  // resources in the resource display.
270  static void Show(bool highlight_background_resources);
271
272  // views::View
273  virtual void Layout();
274  virtual gfx::Size GetPreferredSize();
275  virtual void ViewHierarchyChanged(bool is_add, views::View* parent,
276                                    views::View* child);
277
278  // ButtonListener implementation.
279  virtual void ButtonPressed(views::Button* sender, const views::Event& event);
280
281  // views::DialogDelegate
282  virtual bool CanResize() const;
283  virtual bool CanMaximize() const;
284  virtual bool ExecuteWindowsCommand(int command_id);
285  virtual std::wstring GetWindowTitle() const;
286  virtual std::wstring GetWindowName() const;
287  virtual int GetDialogButtons() const;
288  virtual void WindowClosing();
289  virtual views::View* GetContentsView();
290
291  // views::TableViewObserver implementation.
292  virtual void OnSelectionChanged();
293  virtual void OnDoubleClick();
294  virtual void OnKeyDown(ui::KeyboardCode keycode);
295
296  // views::LinkController implementation.
297  virtual void LinkActivated(views::Link* source, int event_flags);
298
299  // Called by the column picker to pick up any new stat counters that
300  // may have appeared since last time.
301  void UpdateStatsCounters();
302
303  // Menu::Delegate
304  virtual void ShowContextMenuForView(views::View* source,
305                                      const gfx::Point& p,
306                                      bool is_mouse_gesture);
307  virtual bool IsItemChecked(int id) const;
308  virtual void ExecuteCommand(int id);
309
310 private:
311  // Creates the child controls.
312  void Init();
313
314  // Initializes the state of the always-on-top setting as the window is shown.
315  void InitAlwaysOnTopState();
316
317  // Activates the tab associated with the focused row.
318  void ActivateFocusedTab();
319
320  // Adds an always on top item to the window's system menu.
321  void AddAlwaysOnTopSystemMenuItem();
322
323  // Restores saved always on top state from a previous session.
324  bool GetSavedAlwaysOnTopState(bool* always_on_top) const;
325
326  views::NativeButton* purge_memory_button_;
327  views::NativeButton* kill_button_;
328  views::Link* about_memory_link_;
329  views::GroupTableView* tab_table_;
330
331  TaskManager* task_manager_;
332
333  TaskManagerModel* model_;
334
335  // all possible columns, not necessarily visible
336  std::vector<ui::TableColumn> columns_;
337
338  scoped_ptr<TaskManagerTableModel> table_model_;
339
340  // True when the Task Manager window should be shown on top of other windows.
341  bool is_always_on_top_;
342
343  // True when the Task Manager should highlight background resources.
344  bool highlight_background_resources_;
345
346  // We need to own the text of the menu, the Windows API does not copy it.
347  std::wstring always_on_top_menu_text_;
348
349  // An open Task manager window. There can only be one open at a time. This
350  // is reset to NULL when the window is closed.
351  static TaskManagerView* instance_;
352
353  DISALLOW_COPY_AND_ASSIGN(TaskManagerView);
354};
355
356// static
357TaskManagerView* TaskManagerView::instance_ = NULL;
358
359
360TaskManagerView::TaskManagerView(bool highlight_background_resources)
361    : purge_memory_button_(NULL),
362      task_manager_(TaskManager::GetInstance()),
363      model_(TaskManager::GetInstance()->model()),
364      is_always_on_top_(false),
365      highlight_background_resources_(highlight_background_resources) {
366  Init();
367}
368
369TaskManagerView::~TaskManagerView() {
370  // Delete child views now, while our table model still exists.
371  RemoveAllChildViews(true);
372}
373
374void TaskManagerView::Init() {
375  table_model_.reset(new TaskManagerTableModel(model_));
376
377  // Page column has no header label.
378  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_PAGE_COLUMN,
379                                     ui::TableColumn::LEFT, -1, 1));
380  columns_.back().sortable = true;
381  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN,
382                                     ui::TableColumn::RIGHT, -1, 0));
383  columns_.back().sortable = true;
384  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_SHARED_MEM_COLUMN,
385                                     ui::TableColumn::RIGHT, -1, 0));
386  columns_.back().sortable = true;
387  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN,
388                                     ui::TableColumn::RIGHT, -1, 0));
389  columns_.back().sortable = true;
390  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_CPU_COLUMN,
391                                     ui::TableColumn::RIGHT, -1, 0));
392  columns_.back().sortable = true;
393  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_NET_COLUMN,
394                                     ui::TableColumn::RIGHT, -1, 0));
395  columns_.back().sortable = true;
396  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_PROCESS_ID_COLUMN,
397                                     ui::TableColumn::RIGHT, -1, 0));
398  columns_.back().sortable = true;
399  columns_.push_back(ui::TableColumn(
400      IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN,
401      ui::TableColumn::RIGHT, -1, 0));
402  columns_.back().sortable = true;
403  columns_.push_back(ui::TableColumn(
404      IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN,
405      ui::TableColumn::RIGHT, -1, 0));
406  columns_.back().sortable = true;
407  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN,
408                                     ui::TableColumn::RIGHT, -1, 0));
409  columns_.back().sortable = true;
410  columns_.push_back(ui::TableColumn(IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN,
411                                     ui::TableColumn::RIGHT, -1, 0));
412  columns_.back().sortable = true;
413  columns_.push_back(
414      ui::TableColumn(IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN,
415                      ui::TableColumn::RIGHT, -1, 0));
416  columns_.back().sortable = true;
417
418  tab_table_ = new BackgroundColorGroupTableView(
419      table_model_.get(), columns_, highlight_background_resources_);
420
421  // Hide some columns by default
422  tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_PROCESS_ID_COLUMN, false);
423  tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_SHARED_MEM_COLUMN, false);
424  tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN, false);
425  tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN,
426                                  false);
427  tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN,
428                                  false);
429  tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN,
430                                  false);
431  tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN,
432                                  false);
433  tab_table_->SetColumnVisibility(
434      IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN, false);
435  tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN,
436                                  false);
437
438  UpdateStatsCounters();
439  tab_table_->SetObserver(this);
440  tab_table_->SetContextMenuController(this);
441  SetContextMenuController(this);
442  // If we're running with --purge-memory-button, add a "Purge memory" button.
443  if (CommandLine::ForCurrentProcess()->HasSwitch(
444      switches::kPurgeMemoryButton)) {
445    purge_memory_button_ = new views::NativeButton(this,
446        UTF16ToWide(l10n_util::GetStringUTF16(IDS_TASK_MANAGER_PURGE_MEMORY)));
447  }
448  kill_button_ = new views::NativeButton(
449      this, UTF16ToWide(l10n_util::GetStringUTF16(IDS_TASK_MANAGER_KILL)));
450  kill_button_->AddAccelerator(views::Accelerator(ui::VKEY_E,
451                                                  false, false, false));
452  kill_button_->SetAccessibleKeyboardShortcut(L"E");
453  about_memory_link_ = new views::Link(UTF16ToWide(
454      l10n_util::GetStringUTF16(IDS_TASK_MANAGER_ABOUT_MEMORY_LINK)));
455  about_memory_link_->SetController(this);
456
457  // Makes sure our state is consistent.
458  OnSelectionChanged();
459}
460
461void TaskManagerView::UpdateStatsCounters() {
462  base::StatsTable* stats = base::StatsTable::current();
463  if (stats != NULL) {
464    int max = stats->GetMaxCounters();
465    // skip the first row (it's header data)
466    for (int i = 1; i < max; i++) {
467      const char* row = stats->GetRowName(i);
468      if (row != NULL && row[0] != '\0' && !tab_table_->HasColumn(i)) {
469        // TODO(erikkay): Use l10n to get display names for stats.  Right
470        // now we're just displaying the internal counter name.  Perhaps
471        // stat names not in the string table would be filtered out.
472        // TODO(erikkay): Width is hard-coded right now, so many column
473        // names are clipped.
474        ui::TableColumn col(i, ASCIIToUTF16(row), ui::TableColumn::RIGHT, 90,
475                            0);
476        col.sortable = true;
477        columns_.push_back(col);
478        tab_table_->AddColumn(col);
479      }
480    }
481  }
482}
483
484void TaskManagerView::ViewHierarchyChanged(bool is_add,
485                                           views::View* parent,
486                                           views::View* child) {
487  // Since we want the Kill button and the Memory Details link to show up in
488  // the same visual row as the close button, which is provided by the
489  // framework, we must add the buttons to the non-client view, which is the
490  // parent of this view. Similarly, when we're removed from the view
491  // hierarchy, we must take care to clean up those items as well.
492  if (child == this) {
493    if (is_add) {
494      parent->AddChildView(about_memory_link_);
495      if (purge_memory_button_)
496        parent->AddChildView(purge_memory_button_);
497      parent->AddChildView(kill_button_);
498      AddChildView(tab_table_);
499    } else {
500      parent->RemoveChildView(kill_button_);
501      if (purge_memory_button_)
502        parent->RemoveChildView(purge_memory_button_);
503      parent->RemoveChildView(about_memory_link_);
504    }
505  }
506}
507
508void TaskManagerView::Layout() {
509  // views::kPanelHorizMargin is too big.
510  const int kTableButtonSpacing = 12;
511
512  gfx::Size size = kill_button_->GetPreferredSize();
513  int prefered_width = size.width();
514  int prefered_height = size.height();
515
516  tab_table_->SetBounds(
517      x() + views::kPanelHorizMargin,
518      y() + views::kPanelVertMargin,
519      width() - 2 * views::kPanelHorizMargin,
520      height() - 2 * views::kPanelVertMargin - prefered_height);
521
522  // y-coordinate of button top left.
523  gfx::Rect parent_bounds = parent()->GetContentsBounds();
524  int y_buttons =
525      parent_bounds.bottom() - prefered_height - views::kButtonVEdgeMargin;
526
527  kill_button_->SetBounds(
528      x() + width() - prefered_width - views::kPanelHorizMargin,
529      y_buttons,
530      prefered_width,
531      prefered_height);
532
533  if (purge_memory_button_) {
534    size = purge_memory_button_->GetPreferredSize();
535    purge_memory_button_->SetBounds(
536        kill_button_->x() - size.width() -
537            views::kUnrelatedControlHorizontalSpacing,
538        y_buttons, size.width(), size.height());
539  }
540
541  size = about_memory_link_->GetPreferredSize();
542  int link_prefered_width = size.width();
543  int link_prefered_height = size.height();
544  // center between the two buttons horizontally, and line up with
545  // bottom of buttons vertically.
546  int link_y_offset = std::max(0, prefered_height - link_prefered_height) / 2;
547  about_memory_link_->SetBounds(
548      x() + views::kPanelHorizMargin,
549      y_buttons + prefered_height - link_prefered_height - link_y_offset,
550      link_prefered_width,
551      link_prefered_height);
552}
553
554gfx::Size TaskManagerView::GetPreferredSize() {
555  return gfx::Size(kDefaultWidth, kDefaultHeight);
556}
557
558// static
559void TaskManagerView::Show(bool highlight_background_resources) {
560  if (instance_) {
561    if (instance_->highlight_background_resources_ !=
562        highlight_background_resources) {
563      instance_->window()->CloseWindow();
564    } else {
565      // If there's a Task manager window open already, just activate it.
566      instance_->window()->Activate();
567      return;
568    }
569  }
570  instance_ = new TaskManagerView(highlight_background_resources);
571  views::Window::CreateChromeWindow(NULL, gfx::Rect(), instance_);
572  instance_->InitAlwaysOnTopState();
573  instance_->model_->StartUpdating();
574  instance_->window()->Show();
575
576  // Set the initial focus to the list of tasks.
577  views::FocusManager* focus_manager = instance_->GetFocusManager();
578  if (focus_manager)
579    focus_manager->SetFocusedView(instance_->tab_table_);
580}
581
582// ButtonListener implementation.
583void TaskManagerView::ButtonPressed(
584    views::Button* sender, const views::Event& event) {
585  if (purge_memory_button_ && (sender == purge_memory_button_)) {
586    MemoryPurger::PurgeAll();
587  } else {
588    DCHECK_EQ(sender, kill_button_);
589    for (views::TableSelectionIterator iter  = tab_table_->SelectionBegin();
590         iter != tab_table_->SelectionEnd(); ++iter)
591      task_manager_->KillProcess(*iter);
592  }
593}
594
595// DialogDelegate implementation.
596bool TaskManagerView::CanResize() const {
597  return true;
598}
599
600bool TaskManagerView::CanMaximize() const {
601  return true;
602}
603
604bool TaskManagerView::ExecuteWindowsCommand(int command_id) {
605  if (command_id == IDC_ALWAYS_ON_TOP) {
606    is_always_on_top_ = !is_always_on_top_;
607
608    // Change the menu check state.
609    HMENU system_menu = GetSystemMenu(GetWindow()->GetNativeWindow(), FALSE);
610    MENUITEMINFO menu_info;
611    memset(&menu_info, 0, sizeof(MENUITEMINFO));
612    menu_info.cbSize = sizeof(MENUITEMINFO);
613    BOOL r = GetMenuItemInfo(system_menu, IDC_ALWAYS_ON_TOP,
614                             FALSE, &menu_info);
615    DCHECK(r);
616    menu_info.fMask = MIIM_STATE;
617    if (is_always_on_top_)
618      menu_info.fState = MFS_CHECKED;
619    r = SetMenuItemInfo(system_menu, IDC_ALWAYS_ON_TOP, FALSE, &menu_info);
620
621    // Now change the actual window's behavior.
622    window()->SetIsAlwaysOnTop(is_always_on_top_);
623
624    // Save the state.
625    if (g_browser_process->local_state()) {
626      DictionaryPrefUpdate update(g_browser_process->local_state(),
627                                  WideToUTF8(GetWindowName()).c_str());
628      DictionaryValue* window_preferences = update.Get();
629      window_preferences->SetBoolean("always_on_top", is_always_on_top_);
630    }
631    return true;
632  }
633  return false;
634}
635
636std::wstring TaskManagerView::GetWindowTitle() const {
637  return UTF16ToWide(l10n_util::GetStringUTF16(IDS_TASK_MANAGER_TITLE));
638}
639
640std::wstring TaskManagerView::GetWindowName() const {
641  return UTF8ToWide(prefs::kTaskManagerWindowPlacement);
642}
643
644int TaskManagerView::GetDialogButtons() const {
645  return MessageBoxFlags::DIALOGBUTTON_NONE;
646}
647
648void TaskManagerView::WindowClosing() {
649  // Now that the window is closed, we can allow a new one to be opened.
650  // (WindowClosing comes in asynchronously from the call to Close() and we
651  // may have already opened a new instance).
652  if (instance_ == this)
653    instance_ = NULL;
654  task_manager_->OnWindowClosed();
655}
656
657views::View* TaskManagerView::GetContentsView() {
658  return this;
659}
660
661// views::TableViewObserver implementation.
662void TaskManagerView::OnSelectionChanged() {
663  bool selection_contains_browser_process = false;
664  for (views::TableSelectionIterator iter  = tab_table_->SelectionBegin();
665       iter != tab_table_->SelectionEnd(); ++iter) {
666    if (task_manager_->IsBrowserProcess(*iter)) {
667      selection_contains_browser_process = true;
668      break;
669    }
670  }
671  kill_button_->SetEnabled(!selection_contains_browser_process &&
672                           tab_table_->SelectedRowCount() > 0);
673}
674
675void TaskManagerView::OnDoubleClick() {
676  ActivateFocusedTab();
677}
678
679void TaskManagerView::OnKeyDown(ui::KeyboardCode keycode) {
680  if (keycode == ui::VKEY_RETURN)
681    ActivateFocusedTab();
682}
683
684// views::LinkController implementation
685void TaskManagerView::LinkActivated(views::Link* source, int event_flags) {
686  DCHECK(source == about_memory_link_);
687  task_manager_->OpenAboutMemory();
688}
689
690void TaskManagerView::ShowContextMenuForView(views::View* source,
691                                             const gfx::Point& p,
692                                             bool is_mouse_gesture) {
693  UpdateStatsCounters();
694  scoped_ptr<views::Menu> menu(views::Menu::Create(
695      this, views::Menu::TOPLEFT, source->GetWidget()->GetNativeView()));
696  for (std::vector<ui::TableColumn>::iterator i =
697       columns_.begin(); i != columns_.end(); ++i) {
698    menu->AppendMenuItem(i->id, l10n_util::GetStringUTF16(i->id),
699        views::Menu::CHECKBOX);
700  }
701  menu->RunMenuAt(p.x(), p.y());
702}
703
704bool TaskManagerView::IsItemChecked(int id) const {
705  return tab_table_->IsColumnVisible(id);
706}
707
708void TaskManagerView::ExecuteCommand(int id) {
709  tab_table_->SetColumnVisibility(id, !tab_table_->IsColumnVisible(id));
710}
711
712void TaskManagerView::InitAlwaysOnTopState() {
713  is_always_on_top_ = false;
714  if (GetSavedAlwaysOnTopState(&is_always_on_top_))
715    window()->SetIsAlwaysOnTop(is_always_on_top_);
716  AddAlwaysOnTopSystemMenuItem();
717}
718
719void TaskManagerView::ActivateFocusedTab() {
720  int row_count = tab_table_->RowCount();
721  for (int i = 0; i < row_count; ++i) {
722    if (tab_table_->ItemHasTheFocus(i)) {
723      task_manager_->ActivateProcess(i);
724      break;
725    }
726  }
727}
728
729void TaskManagerView::AddAlwaysOnTopSystemMenuItem() {
730  // The Win32 API requires that we own the text.
731  always_on_top_menu_text_ =
732      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ALWAYS_ON_TOP));
733
734  // Let's insert a menu to the window.
735  HMENU system_menu = ::GetSystemMenu(GetWindow()->GetNativeWindow(), FALSE);
736  int index = ::GetMenuItemCount(system_menu) - 1;
737  if (index < 0) {
738    // Paranoia check.
739    NOTREACHED();
740    index = 0;
741  }
742  // First we add the separator.
743  MENUITEMINFO menu_info;
744  memset(&menu_info, 0, sizeof(MENUITEMINFO));
745  menu_info.cbSize = sizeof(MENUITEMINFO);
746  menu_info.fMask = MIIM_FTYPE;
747  menu_info.fType = MFT_SEPARATOR;
748  ::InsertMenuItem(system_menu, index, TRUE, &menu_info);
749
750  // Then the actual menu.
751  menu_info.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_STATE;
752  menu_info.fType = MFT_STRING;
753  menu_info.fState = MFS_ENABLED;
754  if (is_always_on_top_)
755    menu_info.fState |= MFS_CHECKED;
756  menu_info.wID = IDC_ALWAYS_ON_TOP;
757  menu_info.dwTypeData = const_cast<wchar_t*>(always_on_top_menu_text_.c_str());
758  ::InsertMenuItem(system_menu, index, TRUE, &menu_info);
759}
760
761bool TaskManagerView::GetSavedAlwaysOnTopState(bool* always_on_top) const {
762  if (!g_browser_process->local_state())
763    return false;
764
765  const DictionaryValue* dictionary =
766      g_browser_process->local_state()->GetDictionary(
767          WideToUTF8(GetWindowName()).c_str());
768  return dictionary &&
769      dictionary->GetBoolean("always_on_top", always_on_top) && always_on_top;
770}
771
772}  // namespace
773
774namespace browser {
775
776// Declared in browser_dialogs.h so others don't need to depend on our header.
777void ShowTaskManager() {
778  TaskManagerView::Show(false);
779}
780
781void ShowBackgroundPages() {
782  TaskManagerView::Show(true);
783}
784
785}  // namespace browser
786