task_manager_gtk.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 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/gtk/task_manager_gtk.h"
6
7#include <gdk/gdkkeysyms.h>
8
9#include <algorithm>
10#include <set>
11#include <utility>
12#include <vector>
13
14#include "base/auto_reset.h"
15#include "base/command_line.h"
16#include "base/logging.h"
17#include "base/utf_string_conversions.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/defaults.h"
20#include "chrome/browser/memory_purger.h"
21#include "chrome/browser/prefs/pref_service.h"
22#include "chrome/browser/prefs/scoped_user_pref_update.h"
23#include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
24#include "chrome/browser/ui/gtk/gtk_theme_service.h"
25#include "chrome/browser/ui/gtk/gtk_theme_service.h"
26#include "chrome/browser/ui/gtk/gtk_tree.h"
27#include "chrome/browser/ui/gtk/gtk_util.h"
28#include "chrome/browser/ui/gtk/menu_gtk.h"
29#include "chrome/common/chrome_switches.h"
30#include "chrome/common/pref_names.h"
31#include "grit/chromium_strings.h"
32#include "grit/ui_resources.h"
33#include "third_party/skia/include/core/SkBitmap.h"
34#include "ui/base/gtk/gtk_hig_constants.h"
35#include "ui/base/gtk/menu_label_accelerator_util.h"
36#include "ui/base/l10n/l10n_util.h"
37#include "ui/base/models/simple_menu_model.h"
38#include "ui/base/resource/resource_bundle.h"
39#include "ui/gfx/gtk_util.h"
40#include "ui/gfx/image/image.h"
41
42namespace {
43
44// The task manager window default size.
45const int kDefaultWidth = 460;
46const int kDefaultHeight = 270;
47
48// The resource id for the 'End process' button.
49const gint kTaskManagerResponseKill = 1;
50
51// The resource id for the 'Stats for nerds' link button.
52const gint kTaskManagerAboutMemoryLink = 2;
53
54// The resource id for the 'Purge Memory' button
55const gint kTaskManagerPurgeMemory = 3;
56
57enum TaskManagerColumn {
58  kTaskManagerIcon,
59  kTaskManagerTask,
60  kTaskManagerProfileName,
61  kTaskManagerSharedMem,
62  kTaskManagerPrivateMem,
63  kTaskManagerCPU,
64  kTaskManagerNetwork,
65  kTaskManagerProcessID,
66  kTaskManagerJavaScriptMemory,
67  kTaskManagerWebCoreImageCache,
68  kTaskManagerWebCoreScriptsCache,
69  kTaskManagerWebCoreCssCache,
70  kTaskManagerVideoMemory,
71  kTaskManagerFPS,
72  kTaskManagerSqliteMemoryUsed,
73  kTaskManagerGoatsTeleported,
74  // Columns below this point are not visible in the task manager.
75  kTaskManagerBackgroundColor,
76  kTaskManagerColumnCount,
77};
78
79const TaskManagerColumn kTaskManagerLastVisibleColumn =
80    kTaskManagerGoatsTeleported;
81
82static const GdkColor kHighlightColor = GDK_COLOR_RGB(0xff, 0xfa, 0xcd);
83
84TaskManagerColumn TaskManagerResourceIDToColumnID(int id) {
85  switch (id) {
86    case IDS_TASK_MANAGER_TASK_COLUMN:
87      return kTaskManagerTask;
88    case IDS_TASK_MANAGER_PROFILE_NAME_COLUMN:
89      return kTaskManagerProfileName;
90    case IDS_TASK_MANAGER_SHARED_MEM_COLUMN:
91      return kTaskManagerSharedMem;
92    case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN:
93      return kTaskManagerPrivateMem;
94    case IDS_TASK_MANAGER_CPU_COLUMN:
95      return kTaskManagerCPU;
96    case IDS_TASK_MANAGER_NET_COLUMN:
97      return kTaskManagerNetwork;
98    case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
99      return kTaskManagerProcessID;
100    case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
101      return kTaskManagerJavaScriptMemory;
102    case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
103      return kTaskManagerWebCoreImageCache;
104    case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
105      return kTaskManagerWebCoreScriptsCache;
106    case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
107      return kTaskManagerWebCoreCssCache;
108    case IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN:
109      return kTaskManagerVideoMemory;
110    case IDS_TASK_MANAGER_FPS_COLUMN:
111      return kTaskManagerFPS;
112    case IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN:
113      return kTaskManagerSqliteMemoryUsed;
114    case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN:
115      return kTaskManagerGoatsTeleported;
116    default:
117      NOTREACHED();
118      return static_cast<TaskManagerColumn>(-1);
119  }
120}
121
122int TaskManagerColumnIDToResourceID(int id) {
123  switch (id) {
124    case kTaskManagerTask:
125      return IDS_TASK_MANAGER_TASK_COLUMN;
126    case kTaskManagerProfileName:
127      return IDS_TASK_MANAGER_PROFILE_NAME_COLUMN;
128    case kTaskManagerSharedMem:
129      return IDS_TASK_MANAGER_SHARED_MEM_COLUMN;
130    case kTaskManagerPrivateMem:
131      return IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN;
132    case kTaskManagerCPU:
133      return IDS_TASK_MANAGER_CPU_COLUMN;
134    case kTaskManagerNetwork:
135      return IDS_TASK_MANAGER_NET_COLUMN;
136    case kTaskManagerProcessID:
137      return IDS_TASK_MANAGER_PROCESS_ID_COLUMN;
138    case kTaskManagerJavaScriptMemory:
139      return IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN;
140    case kTaskManagerWebCoreImageCache:
141      return IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN;
142    case kTaskManagerWebCoreScriptsCache:
143      return IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN;
144    case kTaskManagerWebCoreCssCache:
145      return IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN;
146    case kTaskManagerVideoMemory:
147      return IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN;
148    case kTaskManagerFPS:
149      return IDS_TASK_MANAGER_FPS_COLUMN;
150    case kTaskManagerSqliteMemoryUsed:
151      return IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN;
152    case kTaskManagerGoatsTeleported:
153      return IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN;
154    default:
155      NOTREACHED();
156      return -1;
157  }
158}
159
160// Should be used for all gtk_tree_view functions that require a column index on
161// input.
162//
163// We need colid - 1 because the gtk_tree_view function is asking for the
164// column index, not the column id, and both kTaskManagerIcon and
165// kTaskManagerTask are in the same column index, so all column IDs are off by
166// one.
167int TreeViewColumnIndexFromID(TaskManagerColumn colid) {
168  return colid - 1;
169}
170
171// Shows or hides a treeview column.
172void TreeViewColumnSetVisible(GtkWidget* treeview, TaskManagerColumn colid,
173                              bool visible) {
174  GtkTreeViewColumn* column = gtk_tree_view_get_column(
175      GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
176  gtk_tree_view_column_set_visible(column, visible);
177}
178
179bool TreeViewColumnIsVisible(GtkWidget* treeview, TaskManagerColumn colid) {
180  GtkTreeViewColumn* column = gtk_tree_view_get_column(
181      GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
182  return gtk_tree_view_column_get_visible(column);
183}
184
185// The task column is special because it has an icon and it gets special
186// treatment with respect to resizing the columns.
187void TreeViewInsertTaskColumn(GtkWidget* treeview, int resid) {
188  int colid = TaskManagerResourceIDToColumnID(resid);
189  GtkTreeViewColumn* column = gtk_tree_view_column_new();
190  gtk_tree_view_column_set_title(column,
191                                 l10n_util::GetStringUTF8(resid).c_str());
192  gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(treeview), colid);
193  GtkCellRenderer* image_renderer = gtk_cell_renderer_pixbuf_new();
194  gtk_tree_view_column_pack_start(column, image_renderer, FALSE);
195  gtk_tree_view_column_add_attribute(column, image_renderer,
196                                     "pixbuf", kTaskManagerIcon);
197  gtk_tree_view_column_add_attribute(column, image_renderer,
198                                     "cell-background-gdk",
199                                     kTaskManagerBackgroundColor);
200  GtkCellRenderer* text_renderer = gtk_cell_renderer_text_new();
201  gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
202  gtk_tree_view_column_add_attribute(column, text_renderer, "markup", colid);
203  gtk_tree_view_column_add_attribute(column, text_renderer,
204                                     "cell-background-gdk",
205                                     kTaskManagerBackgroundColor);
206  gtk_tree_view_column_set_resizable(column, TRUE);
207  // This is temporary: we'll turn expanding off after getting the size.
208  gtk_tree_view_column_set_expand(column, TRUE);
209  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
210  gtk_tree_view_column_set_sort_column_id(column, colid);
211}
212
213// Inserts a column with a column id of |colid| and |name|.
214void TreeViewInsertColumnWithName(GtkWidget* treeview,
215                                  TaskManagerColumn colid, const char* name) {
216  GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
217  gtk_tree_view_insert_column_with_attributes(
218      GTK_TREE_VIEW(treeview), -1,
219      name, renderer,
220      "text", colid,
221      "cell-background-gdk", kTaskManagerBackgroundColor,
222      NULL);
223  GtkTreeViewColumn* column = gtk_tree_view_get_column(
224      GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
225  gtk_tree_view_column_set_resizable(column, TRUE);
226  gtk_tree_view_column_set_sort_column_id(column, colid);
227}
228
229// Loads the column name from |resid| and uses the corresponding
230// TaskManagerColumn value as the column id to insert into the treeview.
231void TreeViewInsertColumn(GtkWidget* treeview, int resid) {
232  TreeViewInsertColumnWithName(treeview, TaskManagerResourceIDToColumnID(resid),
233                               l10n_util::GetStringUTF8(resid).c_str());
234}
235
236// Set the current width of the column without forcing a fixed or maximum
237// width as gtk_tree_view_column_set_[fixed|maximum]_width() would. This would
238// basically be gtk_tree_view_column_set_width() except that there is no such
239// function. It turns out that other applications have done similar hacks to do
240// the same thing - search the web for that nonexistent function name! :)
241void TreeViewColumnSetWidth(GtkTreeViewColumn* column, gint width) {
242  column->width = width;
243  column->resized_width = width;
244  column->use_resized_width = TRUE;
245  // Needed for use_resized_width to be effective.
246  gtk_widget_queue_resize(column->tree_view);
247}
248
249}  // namespace
250
251class TaskManagerGtk::ContextMenuController
252    : public ui::SimpleMenuModel::Delegate {
253 public:
254  explicit ContextMenuController(TaskManagerGtk* task_manager)
255      : task_manager_(task_manager) {
256    menu_model_.reset(new ui::SimpleMenuModel(this));
257    for (int i = kTaskManagerTask; i <= kTaskManagerLastVisibleColumn; i++) {
258      menu_model_->AddCheckItemWithStringId(
259          i, TaskManagerColumnIDToResourceID(i));
260    }
261    menu_.reset(new MenuGtk(NULL, menu_model_.get()));
262  }
263
264  virtual ~ContextMenuController() {}
265
266  void RunMenu(const gfx::Point& point, guint32 event_time) {
267    menu_->PopupAsContext(point, event_time);
268  }
269
270  void Cancel() {
271    task_manager_ = NULL;
272    menu_->Cancel();
273  }
274
275 private:
276  // ui::SimpleMenuModel::Delegate implementation:
277  virtual bool IsCommandIdEnabled(int command_id) const {
278    if (!task_manager_)
279      return false;
280
281    return true;
282  }
283
284  virtual bool IsCommandIdChecked(int command_id) const {
285    if (!task_manager_)
286      return false;
287
288    TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
289    return TreeViewColumnIsVisible(task_manager_->treeview_, colid);
290  }
291
292  virtual bool GetAcceleratorForCommandId(
293      int command_id,
294      ui::Accelerator* accelerator) {
295    return false;
296  }
297
298  virtual void ExecuteCommand(int command_id) {
299    if (!task_manager_)
300      return;
301
302    TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
303    bool visible = !TreeViewColumnIsVisible(task_manager_->treeview_, colid);
304    TreeViewColumnSetVisible(task_manager_->treeview_, colid, visible);
305  }
306
307  // The model and view for the right click context menu.
308  scoped_ptr<ui::SimpleMenuModel> menu_model_;
309  scoped_ptr<MenuGtk> menu_;
310
311  // The TaskManager the context menu was brought up for. Set to NULL when the
312  // menu is canceled.
313  TaskManagerGtk* task_manager_;
314
315  DISALLOW_COPY_AND_ASSIGN(ContextMenuController);
316};
317
318TaskManagerGtk::TaskManagerGtk(bool highlight_background_resources)
319  : task_manager_(TaskManager::GetInstance()),
320    model_(TaskManager::GetInstance()->model()),
321    dialog_(NULL),
322    treeview_(NULL),
323    process_list_(NULL),
324    process_count_(0),
325    ignore_selection_changed_(false),
326    highlight_background_resources_(highlight_background_resources) {
327  Init();
328}
329
330// static
331TaskManagerGtk* TaskManagerGtk::instance_ = NULL;
332
333TaskManagerGtk::~TaskManagerGtk() {
334  model_->RemoveObserver(this);
335  task_manager_->OnWindowClosed();
336
337  gtk_accel_group_disconnect_key(accel_group_, GDK_w, GDK_CONTROL_MASK);
338  gtk_window_remove_accel_group(GTK_WINDOW(dialog_), accel_group_);
339  g_object_unref(accel_group_);
340  accel_group_ = NULL;
341
342  // Disconnect the destroy signal so it doesn't delete |this|.
343  g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_handler_id_);
344  gtk_widget_destroy(dialog_);
345}
346
347////////////////////////////////////////////////////////////////////////////////
348// TaskManagerGtk, TaskManagerModelObserver implementation:
349
350void TaskManagerGtk::OnModelChanged() {
351  // Nothing to do.
352}
353
354void TaskManagerGtk::OnItemsChanged(int start, int length) {
355  GtkTreeIter iter;
356  if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
357                                     NULL, start)) {
358    NOTREACHED() << "Can't get child " << start <<
359        " from GTK_TREE_MODEL(process_list_)";
360  }
361
362  for (int i = start; i < start + length; i++) {
363    SetRowDataFromModel(i, &iter);
364    if (i != start + length - 1) {
365      if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(process_list_), &iter)) {
366        NOTREACHED() << "Can't get next GtkTreeIter object from process_list_ "
367                        "iterator at position " << i;
368      }
369    }
370  }
371}
372
373void TaskManagerGtk::OnItemsAdded(int start, int length) {
374  AutoReset<bool> autoreset(&ignore_selection_changed_, true);
375
376  GtkTreeIter iter;
377  if (start == 0) {
378    gtk_list_store_prepend(process_list_, &iter);
379  } else if (start >= process_count_) {
380    gtk_list_store_append(process_list_, &iter);
381  } else {
382    GtkTreeIter sibling;
383    gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &sibling,
384                                  NULL, start);
385    gtk_list_store_insert_before(process_list_, &iter, &sibling);
386  }
387
388  SetRowDataFromModel(start, &iter);
389
390  for (int i = start + 1; i < start + length; i++) {
391    gtk_list_store_insert_after(process_list_, &iter, &iter);
392    SetRowDataFromModel(i, &iter);
393  }
394
395  process_count_ += length;
396}
397
398void TaskManagerGtk::OnItemsRemoved(int start, int length) {
399  {
400    AutoReset<bool> autoreset(&ignore_selection_changed_, true);
401
402    GtkTreeIter iter;
403    gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
404                                  NULL, start);
405
406    for (int i = 0; i < length; i++) {
407      // |iter| is moved to the next valid node when the current node is
408      // removed.
409      gtk_list_store_remove(process_list_, &iter);
410    }
411
412    process_count_ -= length;
413  }
414
415  // It is possible that we have removed the current selection; run selection
416  // changed to detect that case.
417  OnSelectionChanged(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_)));
418}
419
420////////////////////////////////////////////////////////////////////////////////
421// TaskManagerGtk, public:
422
423void TaskManagerGtk::Close() {
424  // Blow away our dialog - this will cause TaskManagerGtk to free itself.
425  gtk_widget_destroy(dialog_);
426  DCHECK(!instance_);
427}
428
429// static
430void TaskManagerGtk::Show(bool highlight_background_resources) {
431  if (instance_ &&
432      instance_->highlight_background_resources_ !=
433          highlight_background_resources) {
434    instance_->Close();
435    DCHECK(!instance_);
436  }
437
438  if (instance_) {
439    // If there's a Task manager window open already, just activate it.
440    gtk_util::PresentWindow(instance_->dialog_, 0);
441  } else {
442    instance_ = new TaskManagerGtk(highlight_background_resources);
443    instance_->model_->StartUpdating();
444  }
445}
446
447////////////////////////////////////////////////////////////////////////////////
448// TaskManagerGtk, private:
449
450void TaskManagerGtk::Init() {
451  dialog_ = gtk_dialog_new_with_buttons(
452      l10n_util::GetStringUTF8(IDS_TASK_MANAGER_TITLE).c_str(),
453      // Task Manager window is shared between all browsers.
454      NULL,
455      GTK_DIALOG_NO_SEPARATOR,
456      NULL);
457
458  // Allow browser windows to go in front of the task manager dialog in
459  // metacity.
460  gtk_window_set_type_hint(GTK_WINDOW(dialog_), GDK_WINDOW_TYPE_HINT_NORMAL);
461
462  if (CommandLine::ForCurrentProcess()->HasSwitch(
463      switches::kPurgeMemoryButton)) {
464    gtk_dialog_add_button(GTK_DIALOG(dialog_),
465        l10n_util::GetStringUTF8(IDS_TASK_MANAGER_PURGE_MEMORY).c_str(),
466        kTaskManagerPurgeMemory);
467  }
468
469  if (browser_defaults::kShowCancelButtonInTaskManager) {
470    gtk_dialog_add_button(GTK_DIALOG(dialog_),
471        l10n_util::GetStringUTF8(IDS_CLOSE).c_str(),
472        GTK_RESPONSE_DELETE_EVENT);
473  }
474
475  gtk_dialog_add_button(GTK_DIALOG(dialog_),
476      ui::ConvertAcceleratorsFromWindowsStyle(
477          l10n_util::GetStringUTF8(IDS_TASK_MANAGER_KILL)).c_str(),
478      kTaskManagerResponseKill);
479
480  // The response button should not be sensitive when the dialog is first opened
481  // because the selection is initially empty.
482  gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
483                                    kTaskManagerResponseKill, FALSE);
484
485  GtkWidget* link = gtk_chrome_link_button_new(
486      l10n_util::GetStringUTF8(IDS_TASK_MANAGER_ABOUT_MEMORY_LINK).c_str());
487  gtk_dialog_add_action_widget(GTK_DIALOG(dialog_), link,
488                               kTaskManagerAboutMemoryLink);
489
490  // Setting the link widget to secondary positions the button on the left side
491  // of the action area (vice versa for RTL layout).
492  GtkWidget* action_area = gtk_dialog_get_action_area(GTK_DIALOG(dialog_));
493  gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(action_area), link, TRUE);
494
495  ConnectAccelerators();
496
497  GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_));
498  gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
499
500  destroy_handler_id_ = g_signal_connect(dialog_, "destroy",
501                                         G_CALLBACK(OnDestroyThunk), this);
502  g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
503  g_signal_connect(dialog_, "button-press-event",
504                   G_CALLBACK(OnButtonEventThunk), this);
505  gtk_widget_add_events(dialog_,
506                        GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
507
508  // Wrap the treeview widget in a scrolled window in order to have a frame.
509  GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL);
510  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
511                                      GTK_SHADOW_ETCHED_IN);
512  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
513                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
514
515  gtk_container_add(GTK_CONTAINER(content_area), scrolled);
516
517  CreateTaskManagerTreeview();
518  gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview_), TRUE);
519  g_signal_connect(treeview_, "row-activated",
520                   G_CALLBACK(OnRowActivatedThunk), this);
521  g_signal_connect(treeview_, "button-press-event",
522                   G_CALLBACK(OnButtonEventThunk), this);
523
524  // |selection| is owned by |treeview_|.
525  GtkTreeSelection* selection = gtk_tree_view_get_selection(
526      GTK_TREE_VIEW(treeview_));
527  gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
528  g_signal_connect(selection, "changed",
529                   G_CALLBACK(OnSelectionChangedThunk), this);
530
531  gtk_container_add(GTK_CONTAINER(scrolled), treeview_);
532
533  SetInitialDialogSize();
534  gtk_util::ShowDialog(dialog_);
535
536  // If the model already has resources, we need to add them before we start
537  // observing events.
538  if (model_->ResourceCount() > 0)
539    OnItemsAdded(0, model_->ResourceCount());
540
541  model_->AddObserver(this);
542}
543
544void TaskManagerGtk::SetInitialDialogSize() {
545  // Hook up to the realize event so we can size the task column to the
546  // size of the leftover space after packing the other columns.
547  g_signal_connect(treeview_, "realize",
548                   G_CALLBACK(OnTreeViewRealizeThunk), this);
549  // If we previously saved the dialog's bounds, use them.
550  if (g_browser_process->local_state()) {
551    const DictionaryValue* placement_pref =
552        g_browser_process->local_state()->GetDictionary(
553            prefs::kTaskManagerWindowPlacement);
554    int top = 0, left = 0, bottom = 1, right = 1;
555    if (placement_pref &&
556        placement_pref->GetInteger("top", &top) &&
557        placement_pref->GetInteger("left", &left) &&
558        placement_pref->GetInteger("bottom", &bottom) &&
559        placement_pref->GetInteger("right", &right)) {
560      gtk_window_resize(GTK_WINDOW(dialog_),
561                        std::max(1, right - left),
562                        std::max(1, bottom - top));
563      return;
564    }
565  }
566
567  // Otherwise, just set a default size (GTK will override this if it's not
568  // large enough to hold the window's contents).
569  gtk_window_set_default_size(
570      GTK_WINDOW(dialog_), kDefaultWidth, kDefaultHeight);
571}
572
573void TaskManagerGtk::ConnectAccelerators() {
574  accel_group_ = gtk_accel_group_new();
575  gtk_window_add_accel_group(GTK_WINDOW(dialog_), accel_group_);
576
577  gtk_accel_group_connect(accel_group_,
578                          GDK_w, GDK_CONTROL_MASK, GtkAccelFlags(0),
579                          g_cclosure_new(G_CALLBACK(OnGtkAcceleratorThunk),
580                                         this, NULL));
581}
582
583void TaskManagerGtk::CreateTaskManagerTreeview() {
584  process_list_ = gtk_list_store_new(kTaskManagerColumnCount,
585      GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
586      G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
587      G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
588      G_TYPE_STRING, G_TYPE_STRING, GDK_TYPE_COLOR);
589
590  // Support sorting on all columns.
591  process_list_sort_ = gtk_tree_model_sort_new_with_model(
592      GTK_TREE_MODEL(process_list_));
593  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
594                                  kTaskManagerTask,
595                                  ComparePage, this, NULL);
596  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
597                                  kTaskManagerTask,
598                                  CompareProfileName, this, NULL);
599  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
600                                  kTaskManagerSharedMem,
601                                  CompareSharedMemory, this, NULL);
602  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
603                                  kTaskManagerPrivateMem,
604                                  ComparePrivateMemory, this, NULL);
605  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
606                                  kTaskManagerJavaScriptMemory,
607                                  CompareV8Memory, this, NULL);
608  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
609                                  kTaskManagerCPU,
610                                  CompareCPU, this, NULL);
611  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
612                                  kTaskManagerNetwork,
613                                  CompareNetwork, this, NULL);
614  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
615                                  kTaskManagerProcessID,
616                                  CompareProcessID, this, NULL);
617  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
618                                  kTaskManagerWebCoreImageCache,
619                                  CompareWebCoreImageCache, this, NULL);
620  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
621                                  kTaskManagerWebCoreScriptsCache,
622                                  CompareWebCoreScriptsCache, this, NULL);
623  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
624                                  kTaskManagerWebCoreCssCache,
625                                  CompareWebCoreCssCache, this, NULL);
626  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
627                                  kTaskManagerVideoMemory,
628                                  CompareVideoMemory, this, NULL);
629  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
630                                  kTaskManagerFPS,
631                                  CompareFPS, this, NULL);
632  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
633                                  kTaskManagerSqliteMemoryUsed,
634                                  CompareSqliteMemoryUsed, this, NULL);
635  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
636                                  kTaskManagerGoatsTeleported,
637                                  CompareGoatsTeleported, this, NULL);
638  treeview_ = gtk_tree_view_new_with_model(process_list_sort_);
639
640  // Insert all the columns.
641  TreeViewInsertTaskColumn(treeview_, IDS_TASK_MANAGER_TASK_COLUMN);
642  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROFILE_NAME_COLUMN);
643  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SHARED_MEM_COLUMN);
644  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN);
645  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_CPU_COLUMN);
646  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_NET_COLUMN);
647  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROCESS_ID_COLUMN);
648  TreeViewInsertColumn(treeview_,
649                       IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
650  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
651  TreeViewInsertColumn(treeview_,
652                       IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
653  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
654  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN);
655  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_FPS_COLUMN);
656  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN);
657  TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
658
659  // Hide some columns by default.
660  TreeViewColumnSetVisible(treeview_, kTaskManagerProfileName, false);
661  TreeViewColumnSetVisible(treeview_, kTaskManagerSharedMem, false);
662  TreeViewColumnSetVisible(treeview_, kTaskManagerProcessID, false);
663  TreeViewColumnSetVisible(treeview_, kTaskManagerJavaScriptMemory, false);
664  TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreImageCache, false);
665  TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreScriptsCache, false);
666  TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreCssCache, false);
667  TreeViewColumnSetVisible(treeview_, kTaskManagerVideoMemory, false);
668  TreeViewColumnSetVisible(treeview_, kTaskManagerSqliteMemoryUsed, false);
669  TreeViewColumnSetVisible(treeview_, kTaskManagerGoatsTeleported, false);
670
671  g_object_unref(process_list_);
672  g_object_unref(process_list_sort_);
673}
674
675bool IsSharedByGroup(int col_id) {
676  switch (col_id) {
677    case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN:
678    case IDS_TASK_MANAGER_SHARED_MEM_COLUMN:
679    case IDS_TASK_MANAGER_CPU_COLUMN:
680    case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
681    case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
682    case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
683    case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
684    case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
685      return true;
686    default:
687      return false;
688  }
689}
690
691std::string TaskManagerGtk::GetModelText(int row, int col_id) {
692  if (IsSharedByGroup(col_id) && !model_->IsResourceFirstInGroup(row))
693    return std::string();
694
695  switch (col_id) {
696    case IDS_TASK_MANAGER_TASK_COLUMN:  // Process
697      return UTF16ToUTF8(model_->GetResourceTitle(row));
698
699    case IDS_TASK_MANAGER_PROFILE_NAME_COLUMN:  // Profile name
700      return UTF16ToUTF8(model_->GetResourceProfileName(row));
701
702    case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN:  // Memory
703      return UTF16ToUTF8(model_->GetResourcePrivateMemory(row));
704
705    case IDS_TASK_MANAGER_SHARED_MEM_COLUMN:  // Memory
706      return UTF16ToUTF8(model_->GetResourceSharedMemory(row));
707
708    case IDS_TASK_MANAGER_CPU_COLUMN:  // CPU
709      return UTF16ToUTF8(model_->GetResourceCPUUsage(row));
710
711    case IDS_TASK_MANAGER_NET_COLUMN:  // Net
712      return UTF16ToUTF8(model_->GetResourceNetworkUsage(row));
713
714    case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:  // Process ID
715      return UTF16ToUTF8(model_->GetResourceProcessId(row));
716
717    case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
718      return UTF16ToUTF8(model_->GetResourceV8MemoryAllocatedSize(row));
719
720    case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
721      return UTF16ToUTF8(model_->GetResourceWebCoreImageCacheSize(row));
722
723    case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
724      return UTF16ToUTF8(model_->GetResourceWebCoreScriptsCacheSize(row));
725
726    case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
727      return UTF16ToUTF8(model_->GetResourceWebCoreCSSCacheSize(row));
728
729    case IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN:
730      return UTF16ToUTF8(model_->GetResourceVideoMemory(row));
731
732    case IDS_TASK_MANAGER_FPS_COLUMN:
733      return UTF16ToUTF8(model_->GetResourceFPS(row));
734
735    case IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN:
736      return UTF16ToUTF8(model_->GetResourceSqliteMemoryUsed(row));
737
738    case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN:  // Goats Teleported!
739      return UTF16ToUTF8(model_->GetResourceGoatsTeleported(row));
740
741    default:
742      NOTREACHED();
743      return std::string();
744  }
745}
746
747GdkPixbuf* TaskManagerGtk::GetModelIcon(int row) {
748  SkBitmap icon = *model_->GetResourceIcon(row).bitmap();
749  if (icon.pixelRef() ==
750      ui::ResourceBundle::GetSharedInstance().GetImageNamed(
751          IDR_DEFAULT_FAVICON).AsBitmap().pixelRef()) {
752    return static_cast<GdkPixbuf*>(g_object_ref(
753        GtkThemeService::GetDefaultFavicon(true).ToGdkPixbuf()));
754  }
755
756  return gfx::GdkPixbufFromSkBitmap(icon);
757}
758
759void TaskManagerGtk::SetRowDataFromModel(int row, GtkTreeIter* iter) {
760  GdkPixbuf* icon = GetModelIcon(row);
761  std::string task = GetModelText(row, IDS_TASK_MANAGER_TASK_COLUMN);
762  std::string profile_name =
763      GetModelText(row, IDS_TASK_MANAGER_PROFILE_NAME_COLUMN);
764  gchar* task_markup = g_markup_escape_text(task.c_str(), task.length());
765  std::string shared_mem =
766      GetModelText(row, IDS_TASK_MANAGER_SHARED_MEM_COLUMN);
767  std::string priv_mem = GetModelText(row, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN);
768  std::string cpu = GetModelText(row, IDS_TASK_MANAGER_CPU_COLUMN);
769  std::string net = GetModelText(row, IDS_TASK_MANAGER_NET_COLUMN);
770  std::string procid = GetModelText(row, IDS_TASK_MANAGER_PROCESS_ID_COLUMN);
771
772  // Querying the renderer metrics is slow as it has to do IPC, so only do it
773  // when the columns are visible.
774  std::string javascript_memory;
775  if (TreeViewColumnIsVisible(treeview_, kTaskManagerJavaScriptMemory)) {
776    javascript_memory =
777        GetModelText(row, IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
778  }
779  std::string wk_img_cache;
780  if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreImageCache)) {
781    wk_img_cache =
782        GetModelText(row, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
783  }
784  std::string wk_scripts_cache;
785  if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreScriptsCache)) {
786    wk_scripts_cache =
787        GetModelText(row, IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
788  }
789  std::string wk_css_cache;
790  if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreCssCache)) {
791    wk_css_cache =
792        GetModelText(row, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
793  }
794  std::string video_memory;
795  if (TreeViewColumnIsVisible(treeview_, kTaskManagerVideoMemory))
796    video_memory = GetModelText(row, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN);
797  std::string fps;
798  if (TreeViewColumnIsVisible(treeview_, kTaskManagerFPS))
799    fps = GetModelText(row, IDS_TASK_MANAGER_FPS_COLUMN);
800  std::string sqlite_memory;
801  if (TreeViewColumnIsVisible(treeview_, kTaskManagerSqliteMemoryUsed)) {
802    sqlite_memory =
803        GetModelText(row, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN);
804  }
805
806  std::string goats =
807      GetModelText(row, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
808
809  bool is_background = model_->IsBackgroundResource(row) &&
810      highlight_background_resources_;
811  gtk_list_store_set(process_list_, iter,
812                     kTaskManagerIcon, icon,
813                     kTaskManagerTask, task_markup,
814                     kTaskManagerProfileName, profile_name.c_str(),
815                     kTaskManagerSharedMem, shared_mem.c_str(),
816                     kTaskManagerPrivateMem, priv_mem.c_str(),
817                     kTaskManagerCPU, cpu.c_str(),
818                     kTaskManagerNetwork, net.c_str(),
819                     kTaskManagerProcessID, procid.c_str(),
820                     kTaskManagerJavaScriptMemory, javascript_memory.c_str(),
821                     kTaskManagerWebCoreImageCache, wk_img_cache.c_str(),
822                     kTaskManagerWebCoreScriptsCache, wk_scripts_cache.c_str(),
823                     kTaskManagerWebCoreCssCache, wk_css_cache.c_str(),
824                     kTaskManagerVideoMemory, video_memory.c_str(),
825                     kTaskManagerFPS, fps.c_str(),
826                     kTaskManagerSqliteMemoryUsed, sqlite_memory.c_str(),
827                     kTaskManagerGoatsTeleported, goats.c_str(),
828                     kTaskManagerBackgroundColor,
829                     is_background ? &kHighlightColor : NULL,
830                     -1);
831  g_object_unref(icon);
832  g_free(task_markup);
833}
834
835void TaskManagerGtk::KillSelectedProcesses() {
836  GtkTreeSelection* selection = gtk_tree_view_get_selection(
837      GTK_TREE_VIEW(treeview_));
838
839  GtkTreeModel* model;
840  GList* paths = gtk_tree_selection_get_selected_rows(selection, &model);
841  for (GList* item = paths; item; item = item->next) {
842    GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
843        GTK_TREE_MODEL_SORT(process_list_sort_),
844        reinterpret_cast<GtkTreePath*>(item->data));
845    int row = gtk_tree::GetRowNumForPath(path);
846    gtk_tree_path_free(path);
847    task_manager_->KillProcess(row);
848  }
849  g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
850  g_list_free(paths);
851}
852
853void TaskManagerGtk::ShowContextMenu(const gfx::Point& point,
854                                     guint32 event_time) {
855  if (!menu_controller_.get())
856    menu_controller_.reset(new ContextMenuController(this));
857
858  menu_controller_->RunMenu(point, event_time);
859}
860
861void TaskManagerGtk::OnLinkActivated() {
862  task_manager_->OpenAboutMemory();
863}
864
865gint TaskManagerGtk::CompareImpl(GtkTreeModel* model, GtkTreeIter* a,
866                                 GtkTreeIter* b, int id) {
867  int row1 = gtk_tree::GetRowNumForIter(model, b);
868  int row2 = gtk_tree::GetRowNumForIter(model, a);
869
870  // When sorting by non-grouped attributes (e.g., Network), just do a normal
871  // sort.
872  if (!IsSharedByGroup(id))
873    return model_->CompareValues(row1, row2, id);
874
875  // Otherwise, make sure grouped resources are shown together.
876  TaskManagerModel::GroupRange group_range1 =
877      model_->GetGroupRangeForResource(row1);
878  TaskManagerModel::GroupRange group_range2 =
879      model_->GetGroupRangeForResource(row2);
880
881  if (group_range1 == group_range2) {
882    // Sort within groups.
883    // We want the first-in-group row at the top, whether we are sorting up or
884    // down.
885    GtkSortType sort_type;
886    gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(process_list_sort_),
887                                         NULL, &sort_type);
888    if (row1 == group_range1.first)
889      return sort_type == GTK_SORT_ASCENDING ? -1 : 1;
890    if (row2 == group_range2.first)
891      return sort_type == GTK_SORT_ASCENDING ? 1 : -1;
892
893    return model_->CompareValues(row1, row2, id);
894  } else {
895    // Sort between groups.
896    // Compare by the first-in-group rows so that the groups will stay together.
897    return model_->CompareValues(group_range1.first, group_range2.first, id);
898  }
899}
900
901void TaskManagerGtk::OnDestroy(GtkWidget* dialog) {
902  instance_ = NULL;
903  delete this;
904}
905
906void TaskManagerGtk::OnResponse(GtkWidget* dialog, int response_id) {
907  if (response_id == GTK_RESPONSE_DELETE_EVENT) {
908    // Store the dialog's size so we can restore it the next time it's opened.
909    if (g_browser_process->local_state()) {
910      gfx::Rect dialog_bounds = gtk_util::GetDialogBounds(GTK_WIDGET(dialog));
911
912      DictionaryPrefUpdate update(g_browser_process->local_state(),
913                                  prefs::kTaskManagerWindowPlacement);
914      DictionaryValue* placement_pref = update.Get();
915      // Note that we store left/top for consistency with Windows, but that we
916      // *don't* restore them.
917      placement_pref->SetInteger("left", dialog_bounds.x());
918      placement_pref->SetInteger("top", dialog_bounds.y());
919      placement_pref->SetInteger("right", dialog_bounds.right());
920      placement_pref->SetInteger("bottom", dialog_bounds.bottom());
921      placement_pref->SetBoolean("maximized", false);
922    }
923
924    instance_ = NULL;
925    delete this;
926  } else if (response_id == kTaskManagerResponseKill) {
927    KillSelectedProcesses();
928  } else if (response_id == kTaskManagerAboutMemoryLink) {
929    OnLinkActivated();
930  } else if (response_id == kTaskManagerPurgeMemory) {
931    MemoryPurger::PurgeAll();
932  }
933}
934
935void TaskManagerGtk::OnTreeViewRealize(GtkTreeView* treeview) {
936  // Five columns show by default: the task column, the memory column, the CPU
937  // column, the network column, and the FPS column. Initially we set the task
938  // tolumn to take all the extra space, with the other columns being sized to
939  // fit the column names. Here we turn off the expand property of the first
940  // column (to make the table behave sanely when the user resizes it), and set
941  // the effective sizes of all five default columns to the automatically chosen
942  // sizes before any rows are added. This causes them to stay at those sizes
943  // even if the data would overflow, preventing a horizontal scroll bar from
944  // appearing due to the row data.
945  static const TaskManagerColumn dfl_columns[] = {kTaskManagerPrivateMem,
946                                                  kTaskManagerCPU,
947                                                  kTaskManagerNetwork,
948                                                  kTaskManagerFPS};
949  GtkTreeViewColumn* column = NULL;
950  gint width;
951  for (size_t i = 0; i < arraysize(dfl_columns); ++i) {
952    column = gtk_tree_view_get_column(treeview,
953        TreeViewColumnIndexFromID(dfl_columns[i]));
954    width = gtk_tree_view_column_get_width(column);
955    TreeViewColumnSetWidth(column, width);
956  }
957  // Do the task column separately since it's a little different.
958  column = gtk_tree_view_get_column(treeview,
959      TreeViewColumnIndexFromID(kTaskManagerTask));
960  width = gtk_tree_view_column_get_width(column);
961  // Turn expanding back off to make resizing columns behave sanely.
962  gtk_tree_view_column_set_expand(column, FALSE);
963  TreeViewColumnSetWidth(column, width);
964}
965
966void TaskManagerGtk::OnSelectionChanged(GtkTreeSelection* selection) {
967  if (ignore_selection_changed_)
968    return;
969  AutoReset<bool> autoreset(&ignore_selection_changed_, true);
970
971  // The set of groups that should be selected.
972  std::set<TaskManagerModel::GroupRange> ranges;
973  bool selection_contains_browser_process = false;
974
975  GtkTreeModel* model;
976  GList* paths = gtk_tree_selection_get_selected_rows(selection, &model);
977  for (GList* item = paths; item; item = item->next) {
978    GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
979        GTK_TREE_MODEL_SORT(process_list_sort_),
980        reinterpret_cast<GtkTreePath*>(item->data));
981    int row = gtk_tree::GetRowNumForPath(path);
982    gtk_tree_path_free(path);
983    if (task_manager_->IsBrowserProcess(row))
984      selection_contains_browser_process = true;
985    ranges.insert(model_->GetGroupRangeForResource(row));
986  }
987  g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
988  g_list_free(paths);
989
990  for (std::set<TaskManagerModel::GroupRange>::iterator iter = ranges.begin();
991       iter != ranges.end(); ++iter) {
992    for (int i = 0; i < iter->second; ++i) {
993      GtkTreePath* child_path = gtk_tree_path_new_from_indices(iter->first + i,
994                                                               -1);
995      GtkTreePath* sort_path = gtk_tree_model_sort_convert_child_path_to_path(
996        GTK_TREE_MODEL_SORT(process_list_sort_), child_path);
997      gtk_tree_selection_select_path(selection, sort_path);
998      gtk_tree_path_free(child_path);
999      gtk_tree_path_free(sort_path);
1000    }
1001  }
1002
1003  bool sensitive = (paths != NULL) && !selection_contains_browser_process;
1004  gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
1005                                    kTaskManagerResponseKill, sensitive);
1006}
1007
1008void TaskManagerGtk::OnRowActivated(GtkWidget* widget,
1009                                    GtkTreePath* path,
1010                                    GtkTreeViewColumn* column) {
1011  GtkTreePath* child_path = gtk_tree_model_sort_convert_path_to_child_path(
1012      GTK_TREE_MODEL_SORT(process_list_sort_), path);
1013  int row = gtk_tree::GetRowNumForPath(child_path);
1014  gtk_tree_path_free(child_path);
1015  task_manager_->ActivateProcess(row);
1016}
1017
1018gboolean TaskManagerGtk::OnButtonEvent(GtkWidget* widget,
1019                                       GdkEventButton* event) {
1020  // GTK does menu on mouse-up while views does menu on mouse-down,
1021  // so this function can be called from either signal.
1022  if (event->button == 3) {
1023    ShowContextMenu(gfx::Point(event->x_root, event->y_root),
1024                    event->time);
1025    return TRUE;
1026  }
1027
1028  return FALSE;
1029}
1030
1031gboolean TaskManagerGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
1032                                          GObject* acceleratable,
1033                                          guint keyval,
1034                                          GdkModifierType modifier) {
1035  if (keyval == GDK_w && modifier == GDK_CONTROL_MASK) {
1036    // The GTK_RESPONSE_DELETE_EVENT response must be sent before the widget
1037    // is destroyed.  The deleted object will receive gtk signals otherwise.
1038    gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT);
1039  }
1040
1041  return TRUE;
1042}
1043